insforge 1.2.10 → 1.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +20 -20
- package/.dockerignore +60 -60
- package/.env.example +83 -77
- package/.github/ISSUE_TEMPLATE/bug_report.yml +36 -36
- package/.github/ISSUE_TEMPLATE/config.yml +11 -11
- package/.github/ISSUE_TEMPLATE/feature_request.yml +26 -26
- package/.github/PULL_REQUEST_TEMPLATE.md +7 -7
- package/.github/copilot-instructions.md +146 -146
- package/.github/workflows/build-image.yml +65 -65
- package/.github/workflows/ci-premerge-check.yml +23 -23
- package/.github/workflows/e2e.yml +63 -63
- package/.github/workflows/lint-and-format.yml +32 -32
- package/.prettierignore +64 -64
- package/CHANGELOG.md +46 -44
- package/CLAUDE_PLUGIN.md +104 -104
- package/CODE_OF_CONDUCT.md +128 -128
- package/CONTRIBUTING.md +125 -125
- package/Dockerfile +30 -30
- package/GITHUB_OAUTH_SETUP.md +49 -49
- package/GOOGLE_OAUTH_SETUP.md +148 -148
- package/LICENSE +201 -201
- package/README.md +182 -182
- package/assets/Dark.svg +23 -23
- package/auth/package.json +30 -28
- package/auth/src/lib/broadcastService.ts +4 -4
- package/auth/src/lib/insforge.ts +8 -0
- package/auth/src/main.tsx +2 -4
- package/auth/src/pages/SignInPage.tsx +5 -2
- package/auth/src/pages/SignUpPage.tsx +5 -2
- package/auth/src/pages/VerifyEmailPage.tsx +18 -0
- package/auth/tsconfig.json +33 -32
- package/auth/tsconfig.node.json +11 -11
- package/backend/package.json +82 -75
- package/backend/src/api/middlewares/rate-limiters.ts +127 -127
- package/backend/src/api/routes/ai/index.routes.ts +475 -468
- package/backend/src/api/routes/auth/index.routes.ts +720 -570
- package/backend/src/api/routes/auth/oauth.routes.ts +478 -448
- package/backend/src/api/routes/database/advance.routes.ts +37 -16
- package/backend/src/api/routes/database/index.routes.ts +80 -1
- package/backend/src/api/routes/database/records.routes.ts +48 -184
- package/backend/src/api/routes/database/rpc.routes.ts +69 -0
- package/backend/src/api/routes/database/tables.routes.ts +0 -14
- package/backend/src/api/routes/deployments/index.routes.ts +192 -0
- package/backend/src/api/routes/docs/index.routes.ts +76 -76
- package/backend/src/api/routes/email/index.routes.ts +35 -0
- package/backend/src/api/routes/functions/index.routes.ts +21 -15
- package/backend/src/api/routes/metadata/index.routes.ts +38 -0
- package/backend/src/api/routes/realtime/channels.routes.ts +81 -0
- package/backend/src/api/routes/realtime/index.routes.ts +12 -0
- package/backend/src/api/routes/realtime/messages.routes.ts +48 -0
- package/backend/src/api/routes/realtime/permissions.routes.ts +19 -0
- package/backend/src/api/routes/storage/index.routes.ts +18 -12
- package/backend/src/api/routes/usage/index.routes.ts +6 -4
- package/backend/src/api/routes/webhooks/index.routes.ts +109 -0
- package/backend/src/infra/database/database.manager.ts +14 -11
- package/backend/src/infra/database/migrations/000_create-base-tables.sql +141 -141
- package/backend/src/infra/database/migrations/001_create-helper-functions.sql +40 -40
- package/backend/src/infra/database/migrations/002_rename-auth-tables.sql +29 -29
- package/backend/src/infra/database/migrations/003_create-users-table.sql +55 -55
- package/backend/src/infra/database/migrations/004_add-reload-postgrest-func.sql +23 -23
- package/backend/src/infra/database/migrations/005_enable-project-admin-modify-users.sql +29 -29
- package/backend/src/infra/database/migrations/006_modify-ai-usage-table.sql +24 -24
- package/backend/src/infra/database/migrations/007_drop-metadata-table.sql +1 -1
- package/backend/src/infra/database/migrations/008_add-system-tables.sql +76 -76
- package/backend/src/infra/database/migrations/009_add-function-secrets.sql +23 -23
- package/backend/src/infra/database/migrations/010_modify-ai-config-modalities.sql +93 -93
- package/backend/src/infra/database/migrations/011_refactor-secrets-table.sql +15 -15
- package/backend/src/infra/database/migrations/012_add-storage-uploaded-by.sql +7 -7
- package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -44
- package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +7 -7
- package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +59 -59
- package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -24
- package/backend/src/infra/database/migrations/017_create-realtime-schema.sql +233 -0
- package/backend/src/infra/database/migrations/018_schema-rework.sql +441 -0
- package/backend/src/infra/database/migrations/019_create-deployments-table.sql +36 -0
- package/backend/src/infra/database/migrations/020_add-audio-modality.sql +11 -0
- package/backend/src/infra/database/migrations/bootstrap/bootstrap-migrations.js +103 -0
- package/backend/src/infra/realtime/realtime.manager.ts +246 -0
- package/backend/src/infra/realtime/webhook-sender.ts +82 -0
- package/backend/src/infra/security/token.manager.ts +216 -125
- package/backend/src/infra/socket/socket.manager.ts +198 -64
- package/backend/src/providers/ai/openrouter.provider.ts +24 -12
- package/backend/src/providers/database/base.provider.ts +39 -0
- package/backend/src/providers/database/cloud.provider.ts +159 -0
- package/backend/src/providers/deployments/vercel.provider.ts +516 -0
- package/backend/src/providers/email/base.provider.ts +4 -7
- package/backend/src/providers/email/cloud.provider.ts +84 -0
- package/backend/src/providers/oauth/apple.provider.ts +266 -0
- package/backend/src/providers/oauth/index.ts +1 -0
- package/backend/src/server.ts +329 -284
- package/backend/src/services/ai/ai-config.service.ts +6 -6
- package/backend/src/services/ai/ai-model.service.ts +60 -60
- package/backend/src/services/ai/ai-usage.service.ts +7 -7
- package/backend/src/services/ai/chat-completion.service.ts +415 -220
- package/backend/src/services/ai/helpers.ts +64 -64
- package/backend/src/services/ai/image-generation.service.ts +3 -3
- package/backend/src/services/ai/index.ts +13 -13
- package/backend/src/services/auth/auth-config.service.ts +4 -4
- package/backend/src/services/auth/auth-otp.service.ts +6 -6
- package/backend/src/services/auth/auth.service.ts +148 -74
- package/backend/src/services/auth/index.ts +4 -4
- package/backend/src/services/auth/oauth-config.service.ts +12 -12
- package/backend/src/services/database/database-advance.service.ts +19 -55
- package/backend/src/services/database/database-table.service.ts +38 -94
- package/backend/src/services/database/database.service.ts +127 -0
- package/backend/src/services/database/postgrest-proxy.service.ts +165 -0
- package/backend/src/services/deployments/deployment.service.ts +693 -0
- package/backend/src/services/email/email.service.ts +5 -7
- package/backend/src/services/functions/function.service.ts +61 -41
- package/backend/src/services/logs/audit.service.ts +10 -10
- package/backend/src/services/realtime/index.ts +3 -0
- package/backend/src/services/realtime/realtime-auth.service.ts +104 -0
- package/backend/src/services/realtime/realtime-channel.service.ts +237 -0
- package/backend/src/services/realtime/realtime-message.service.ts +260 -0
- package/backend/src/services/secrets/secret.service.ts +101 -27
- package/backend/src/services/storage/storage.service.ts +30 -30
- package/backend/src/services/usage/usage.service.ts +6 -6
- package/backend/src/types/ai.ts +8 -0
- package/backend/src/types/auth.ts +16 -1
- package/backend/src/types/database.ts +2 -0
- package/backend/src/types/deployments.ts +33 -0
- package/backend/src/types/realtime.ts +18 -0
- package/backend/src/types/socket.ts +7 -31
- package/backend/src/types/storage.ts +1 -1
- package/backend/src/types/webhooks.ts +45 -0
- package/backend/src/utils/cookies.ts +34 -0
- package/backend/src/utils/environment.ts +0 -14
- package/backend/src/utils/s3-config-loader.ts +64 -0
- package/backend/src/utils/seed.ts +79 -43
- package/backend/src/utils/sql-parser.ts +216 -0
- package/backend/src/utils/utils.ts +114 -114
- package/backend/src/utils/validations.ts +10 -10
- package/backend/tests/README.md +133 -133
- package/backend/tests/cleanup-all-test-data.sh +230 -230
- package/backend/tests/cloud/test-s3-multitenant.sh +131 -131
- package/backend/tests/local/comprehensive-curl-tests.sh +155 -155
- package/backend/tests/local/test-ai-config.sh +129 -129
- package/backend/tests/local/test-ai-usage.sh +80 -80
- package/backend/tests/local/test-auth-router.sh +143 -143
- package/backend/tests/local/test-database-router.sh +222 -222
- package/backend/tests/local/test-e2e.sh +240 -240
- package/backend/tests/local/test-fk-errors.sh +96 -96
- package/backend/tests/local/test-functions.sh +123 -123
- package/backend/tests/local/test-id-field.sh +200 -200
- package/backend/tests/local/test-logs.sh +132 -132
- package/backend/tests/local/test-public-bucket.sh +264 -264
- package/backend/tests/local/test-rpc.sh +141 -0
- package/backend/tests/local/test-secrets.sh +249 -249
- package/backend/tests/local/test-serverless-functions.sh.disabled +325 -325
- package/backend/tests/local/test-traditional-rest.sh +208 -208
- package/backend/tests/manual/README.md +50 -50
- package/backend/tests/manual/create-large-table-simple.sql +10 -10
- package/backend/tests/manual/seed-large-table.sql +100 -100
- package/backend/tests/manual/setup-large-table-extras.sql +33 -33
- package/backend/tests/manual/test-ai-model-plugins.sh +258 -0
- package/backend/tests/manual/test-bulk-upsert.sh +409 -409
- package/backend/tests/manual/test-database-advance.sh +296 -296
- package/backend/tests/manual/test-postgrest-stability.sh +191 -191
- package/backend/tests/manual/test-rawsql-export-import.sh +411 -411
- package/backend/tests/manual/test-rawsql-modes.sh +244 -244
- package/backend/tests/manual/test-universal-storage.sh +263 -263
- package/backend/tests/manual/test-users.sql +17 -17
- package/backend/tests/run-all-tests.sh +139 -139
- package/backend/tests/setup.ts +0 -0
- package/backend/tests/test-config.sh +338 -338
- package/backend/tests/unit/analyze-query.test.ts +697 -0
- package/backend/tests/unit/database-advance.test.ts +326 -0
- package/backend/tests/unit/helpers.test.ts +2 -2
- package/backend/tsconfig.json +22 -22
- package/claude-plugin/.claude-plugin/plugin.json +24 -24
- package/claude-plugin/README.md +133 -133
- package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +273 -270
- package/docker-compose.prod.yml +204 -200
- package/docker-compose.yml +232 -228
- package/docker-init/db/db-init.sql +97 -97
- package/docker-init/db/jwt.sql +5 -5
- package/docker-init/db/postgresql.conf +16 -16
- package/docker-init/logs/vector.yml +236 -236
- package/docs/README.md +44 -44
- package/docs/agent-docs/deployment.md +79 -0
- package/docs/agent-docs/real-time.md +269 -0
- package/docs/changelog.mdx +212 -67
- package/docs/core-concepts/ai/architecture.mdx +350 -372
- package/docs/core-concepts/ai/sdk.mdx +238 -213
- package/docs/core-concepts/authentication/architecture.mdx +276 -278
- package/docs/core-concepts/authentication/sdk.mdx +710 -414
- package/docs/core-concepts/authentication/ui-components/customization.mdx +733 -529
- package/docs/core-concepts/authentication/ui-components/nextjs.mdx +247 -221
- package/docs/core-concepts/authentication/ui-components/react-router.mdx +183 -184
- package/docs/core-concepts/authentication/ui-components/react.mdx +136 -129
- package/docs/core-concepts/database/architecture.mdx +292 -255
- package/docs/core-concepts/database/pgvector.mdx +138 -0
- package/docs/core-concepts/database/sdk.mdx +382 -382
- package/docs/core-concepts/deployments/architecture.mdx +152 -0
- package/docs/core-concepts/email/architecture.mdx +103 -0
- package/docs/core-concepts/email/sdk.mdx +53 -0
- package/docs/core-concepts/functions/architecture.mdx +105 -105
- package/docs/core-concepts/functions/sdk.mdx +183 -184
- package/docs/core-concepts/realtime/architecture.mdx +446 -0
- package/docs/core-concepts/realtime/sdk.mdx +409 -0
- package/docs/core-concepts/storage/architecture.mdx +243 -243
- package/docs/core-concepts/storage/sdk.mdx +253 -253
- package/docs/deployment/README.md +94 -94
- package/docs/deployment/deploy-to-aws-ec2.md +564 -564
- package/docs/deployment/deploy-to-azure-virtual-machines.md +312 -312
- package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -613
- package/docs/deployment/deploy-to-render.md +441 -441
- package/docs/deprecated/insforge-auth-api.md +214 -214
- package/docs/deprecated/insforge-auth-sdk.md +99 -99
- package/docs/deprecated/insforge-db-api.md +358 -358
- package/docs/deprecated/insforge-db-sdk.md +139 -139
- package/docs/deprecated/insforge-debug-sdk.md +156 -156
- package/docs/deprecated/insforge-debug.md +64 -64
- package/docs/deprecated/insforge-instructions.md +123 -123
- package/docs/deprecated/insforge-project.md +117 -117
- package/docs/deprecated/insforge-storage-api.md +278 -278
- package/docs/deprecated/insforge-storage-sdk.md +158 -158
- package/docs/docs.json +240 -210
- package/docs/examples/framework-guides/nextjs.mdx +131 -131
- package/docs/examples/framework-guides/nuxt.mdx +165 -165
- package/docs/examples/framework-guides/react.mdx +165 -165
- package/docs/examples/framework-guides/svelte.mdx +153 -153
- package/docs/examples/framework-guides/vue.mdx +159 -159
- package/docs/examples/overview.mdx +67 -67
- package/docs/favicon.png +0 -0
- package/docs/favicon.svg +4 -19
- package/docs/images/changelog/dec-2025/ai-integration.png +0 -0
- package/docs/images/changelog/dec-2025/ai-models.webp +0 -0
- package/docs/images/changelog/dec-2025/alipay-payment.webp +0 -0
- package/docs/images/changelog/dec-2025/apple-login.jpg +0 -0
- package/docs/images/changelog/dec-2025/apple-oauth.mp4 +0 -0
- package/docs/images/changelog/dec-2025/mcp-installer.png +0 -0
- package/docs/images/changelog/dec-2025/moreModels.png +0 -0
- package/docs/images/changelog/dec-2025/multi-region.webp +0 -0
- package/docs/images/changelog/dec-2025/postgres-connection.webp +0 -0
- package/docs/images/changelog/dec-2025/realtime-module.jpg +0 -0
- package/docs/images/changelog/dec-2025/realtime2.png +0 -0
- package/docs/images/icons/ai.svg +4 -4
- package/docs/images/logos/nextjs.svg +4 -4
- package/docs/images/logos/nuxt.svg +4 -4
- package/docs/images/logos/react.svg +5 -5
- package/docs/images/logos/svelte.svg +4 -4
- package/docs/images/logos/vue.svg +5 -5
- package/docs/images/mcp-setup/CC-MCP-1.mp4 +0 -0
- package/docs/images/mcp-setup/CC-MCP-2.mp4 +0 -0
- package/docs/images/mcp-setup/Cursor-MCP-1.mp4 +0 -0
- package/docs/images/mcp-setup/Cursor-MCP-2.mp4 +0 -0
- package/docs/images/mcp-setup/Cursor-MCP-3.mp4 +0 -0
- package/docs/images/mcp-setup/claude-code-connect.png +0 -0
- package/docs/images/mcp-setup/cline-1.png +0 -0
- package/docs/images/mcp-setup/cline-2.png +0 -0
- package/docs/images/mcp-setup/cline-3.png +0 -0
- package/docs/images/mcp-setup/connect-project.png +0 -0
- package/docs/images/mcp-setup/copilot-1.png +0 -0
- package/docs/images/mcp-setup/copilot-2.png +0 -0
- package/docs/images/mcp-setup/copilot-3.png +0 -0
- package/docs/images/mcp-setup/mcp-json-1.png +0 -0
- package/docs/images/mcp-setup/mcp-json-2.png +0 -0
- package/docs/images/mcp-setup/qoder-1.png +0 -0
- package/docs/images/mcp-setup/qoder-2.png +0 -0
- package/docs/images/mcp-setup/roocode-1.png +0 -0
- package/docs/images/mcp-setup/roocode-2.png +0 -0
- package/docs/images/mcp-setup/trae-1.png +0 -0
- package/docs/images/mcp-setup/trae-2.png +0 -0
- package/docs/images/mcp-setup/trae-3.png +0 -0
- package/docs/images/mcp-setup/trae-4.png +0 -0
- package/docs/images/mcp-setup/trae-5.png +0 -0
- package/docs/images/mcp-setup/windsurf-1.png +0 -0
- package/docs/images/mcp-setup/windsurf-2.png +0 -0
- package/docs/insforge-instructions-sdk.md +93 -88
- package/docs/introduction.mdx +46 -45
- package/docs/logo/dark.svg +22 -22
- package/docs/logo/light.svg +20 -20
- package/docs/mcp-setup.mdx +332 -0
- package/docs/oauth-server.mdx +563 -0
- package/docs/partnership.mdx +720 -646
- package/docs/quickstart.mdx +82 -82
- package/docs/showcase.mdx +52 -52
- package/docs/snippets/sdk-installation.mdx +21 -21
- package/docs/snippets/service-icons.mdx +27 -27
- package/docs/vscode-extension.mdx +74 -0
- package/eslint.config.js +1 -0
- package/examples/oauth/frontend-oauth-example.html +250 -250
- package/examples/response-examples.md +443 -443
- package/frontend/components.json +17 -17
- package/frontend/package.json +69 -69
- package/frontend/src/App.tsx +8 -3
- package/frontend/src/assets/icons/checkbox_checked.svg +6 -6
- package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -6
- package/frontend/src/assets/icons/checked.svg +3 -3
- package/frontend/src/assets/icons/connected.svg +3 -3
- package/frontend/src/assets/icons/error.svg +3 -3
- package/frontend/src/assets/icons/loader.svg +9 -9
- package/frontend/src/assets/icons/pencil.svg +4 -4
- package/frontend/src/assets/icons/refresh.svg +4 -4
- package/frontend/src/assets/icons/step_active.svg +3 -3
- package/frontend/src/assets/icons/step_inactive.svg +11 -11
- package/frontend/src/assets/icons/warning.svg +3 -3
- package/frontend/src/assets/logos/antigravity.svg +1 -0
- package/frontend/src/assets/logos/apple.svg +3 -3
- package/frontend/src/assets/logos/claude_code.svg +3 -3
- package/frontend/src/assets/logos/cline.svg +6 -6
- package/frontend/src/assets/logos/copilot.svg +10 -0
- package/frontend/src/assets/logos/cursor.svg +20 -20
- package/frontend/src/assets/logos/deepseek.svg +139 -0
- package/frontend/src/assets/logos/discord.svg +8 -8
- package/frontend/src/assets/logos/facebook.svg +3 -3
- package/frontend/src/assets/logos/gemini.svg +19 -19
- package/frontend/src/assets/logos/github.svg +5 -5
- package/frontend/src/assets/logos/google.svg +13 -13
- package/frontend/src/assets/logos/grok.svg +10 -10
- package/frontend/src/assets/logos/insforge_dark.svg +15 -15
- package/frontend/src/assets/logos/insforge_light.svg +15 -15
- package/frontend/src/assets/logos/instagram.svg +1 -1
- package/frontend/src/assets/logos/kiro.svg +9 -0
- package/frontend/src/assets/logos/linkedin.svg +3 -3
- package/frontend/src/assets/logos/openai.svg +10 -10
- package/frontend/src/assets/logos/qoder.svg +4 -0
- package/frontend/src/assets/logos/qwen.svg +15 -0
- package/frontend/src/assets/logos/roo_code.svg +9 -9
- package/frontend/src/assets/logos/spotify.svg +16 -16
- package/frontend/src/assets/logos/tiktok.svg +5 -5
- package/frontend/src/assets/logos/trae.svg +3 -3
- package/frontend/src/assets/logos/windsurf.svg +10 -10
- package/frontend/src/assets/logos/x.svg +3 -3
- package/frontend/src/components/CodeBlock.tsx +2 -2
- package/frontend/src/components/ConnectCTA.tsx +3 -2
- package/frontend/src/components/datagrid/DataGrid.tsx +90 -62
- package/frontend/src/components/datagrid/datagridTypes.tsx +2 -1
- package/frontend/src/components/datagrid/index.ts +1 -1
- package/frontend/src/components/index.ts +0 -1
- package/frontend/src/components/layout/AppHeader.tsx +13 -37
- package/frontend/src/components/layout/AppSidebar.tsx +85 -100
- package/frontend/src/components/layout/Layout.tsx +34 -32
- package/frontend/src/components/layout/PrimaryMenu.tsx +12 -4
- package/frontend/src/components/radix/Select.tsx +151 -151
- package/frontend/src/features/ai/components/AIConfigCard.tsx +200 -200
- package/frontend/src/features/ai/components/AIEmptyState.tsx +23 -23
- package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +102 -101
- package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -135
- package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -51
- package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -118
- package/frontend/src/features/ai/components/index.ts +6 -6
- package/frontend/src/features/ai/helpers.ts +147 -141
- package/frontend/src/features/ai/{page → pages}/AIPage.tsx +166 -166
- package/frontend/src/features/auth/components/AuthPreview.tsx +96 -96
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +1 -0
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +61 -31
- package/frontend/src/features/auth/components/index.ts +5 -5
- package/frontend/src/features/auth/helpers.tsx +8 -0
- package/frontend/src/features/auth/{page → pages}/AuthMethodsPage.tsx +275 -275
- package/frontend/src/features/auth/{page → pages}/UsersPage.tsx +0 -28
- package/frontend/src/features/dashboard/{page → pages}/DashboardPage.tsx +1 -1
- package/frontend/src/features/database/components/DatabaseDataGrid.tsx +0 -2
- package/frontend/src/features/database/components/ForeignKeyCell.tsx +38 -11
- package/frontend/src/features/database/components/ForeignKeyPopover.tsx +18 -8
- package/frontend/src/features/database/components/LinkRecordModal.tsx +61 -13
- package/frontend/src/features/database/components/RecordFormField.tsx +1 -1
- package/frontend/src/features/database/components/SQLModal.tsx +75 -0
- package/frontend/src/features/database/components/TableForm.tsx +0 -4
- package/frontend/src/features/database/components/TableSidebar.tsx +0 -3
- package/frontend/src/features/database/components/TablesEmptyState.tsx +1 -1
- package/frontend/src/features/database/components/TemplatePreview.tsx +1 -2
- package/frontend/src/features/database/constants.ts +16 -28
- package/frontend/src/features/database/hooks/useCSVImport.ts +3 -2
- package/frontend/src/features/database/hooks/useDatabase.ts +66 -0
- package/frontend/src/features/database/hooks/useRawSQL.ts +3 -2
- package/frontend/src/features/database/hooks/useTables.ts +30 -28
- package/frontend/src/features/database/index.ts +1 -0
- package/frontend/src/features/database/{page → pages}/FunctionsPage.tsx +29 -42
- package/frontend/src/features/database/{page → pages}/IndexesPage.tsx +34 -51
- package/frontend/src/features/database/{page → pages}/PoliciesPage.tsx +42 -58
- package/frontend/src/features/database/{page → pages}/SQLEditorPage.tsx +2 -2
- package/frontend/src/features/database/{page → pages}/TablesPage.tsx +0 -42
- package/frontend/src/features/database/{page → pages}/TriggersPage.tsx +34 -51
- package/frontend/src/features/database/services/advance.service.ts +1 -41
- package/frontend/src/features/database/services/database.service.ts +55 -0
- package/frontend/src/features/database/services/record.service.ts +4 -20
- package/frontend/src/features/database/services/table.service.ts +1 -10
- package/frontend/src/features/database/templates/ai-chatbot.ts +6 -6
- package/frontend/src/features/database/templates/ecommerce-platform.ts +2 -2
- package/frontend/src/features/database/templates/instagram-clone.ts +10 -10
- package/frontend/src/features/database/templates/notion-clone.ts +8 -8
- package/frontend/src/features/database/templates/reddit-clone.ts +10 -10
- package/frontend/src/features/deployments/components/DeploymentRow.tsx +93 -0
- package/frontend/src/features/deployments/components/DeploymentsEmptyState.tsx +15 -0
- package/frontend/src/features/deployments/hooks/useDeployments.ts +157 -0
- package/frontend/src/features/deployments/pages/DeploymentsPage.tsx +318 -0
- package/frontend/src/features/deployments/services/deployments.service.ts +63 -0
- package/frontend/src/features/functions/components/FunctionRow.tsx +72 -72
- package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -56
- package/frontend/src/features/functions/components/SecretRow.tsx +3 -3
- package/frontend/src/features/functions/components/index.ts +5 -5
- package/frontend/src/features/functions/hooks/useFunctions.ts +5 -4
- package/frontend/src/features/functions/hooks/useSecrets.ts +6 -9
- package/frontend/src/features/functions/{page → pages}/FunctionsPage.tsx +21 -44
- package/frontend/src/features/functions/{page → pages}/SecretsPage.tsx +118 -116
- package/frontend/src/features/functions/services/function.service.ts +8 -25
- package/frontend/src/features/functions/services/secret.service.ts +23 -41
- package/frontend/src/features/login/{page → pages}/CloudLoginPage.tsx +125 -118
- package/frontend/src/features/logs/components/LogDetailPanel.tsx +41 -0
- package/frontend/src/features/logs/components/LogsDataGrid.tsx +32 -1
- package/frontend/src/features/logs/components/index.ts +1 -0
- package/frontend/src/features/logs/hooks/useMcpUsage.ts +13 -66
- package/frontend/src/features/logs/{page → pages}/LogsPage.tsx +36 -6
- package/frontend/src/features/onboard/components/ApiCredentialsSection.tsx +59 -0
- package/frontend/src/features/onboard/components/ConnectionStringSection.tsx +180 -0
- package/frontend/src/features/onboard/components/McpConnectionSection.tsx +159 -0
- package/frontend/src/features/onboard/components/OnboardingController.tsx +68 -0
- package/frontend/src/features/onboard/components/OnboardingModal.tsx +121 -267
- package/frontend/src/features/onboard/components/ShowPasswordButton.tsx +21 -0
- package/frontend/src/features/onboard/components/index.ts +9 -4
- package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +1 -1
- package/frontend/src/features/onboard/components/mcp/QoderDeeplinkGenerator.tsx +36 -0
- package/frontend/src/features/onboard/components/mcp/helpers.tsx +123 -98
- package/frontend/src/features/onboard/components/mcp/index.ts +4 -3
- package/frontend/src/features/onboard/index.ts +17 -13
- package/frontend/src/features/realtime/components/ChannelRow.tsx +83 -0
- package/frontend/src/features/realtime/components/EditChannelModal.tsx +246 -0
- package/frontend/src/features/realtime/components/MessageRow.tsx +85 -0
- package/frontend/src/features/realtime/components/RealtimeEmptyState.tsx +30 -0
- package/frontend/src/features/realtime/hooks/useRealtime.ts +218 -0
- package/frontend/src/features/realtime/index.ts +11 -0
- package/frontend/src/features/realtime/pages/RealtimeChannelsPage.tsx +172 -0
- package/frontend/src/features/realtime/pages/RealtimeMessagesPage.tsx +211 -0
- package/frontend/src/features/realtime/pages/RealtimePermissionsPage.tsx +191 -0
- package/frontend/src/features/realtime/services/realtime.service.ts +107 -0
- package/frontend/src/features/settings/pages/SettingsPage.tsx +349 -0
- package/frontend/src/features/storage/{page → pages}/StoragePage.tsx +1 -29
- package/frontend/src/features/visualizer/components/AuthNode.tsx +4 -4
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +24 -11
- package/frontend/src/features/visualizer/{page → pages}/VisualizerPage.tsx +11 -36
- package/frontend/src/index.css +249 -249
- package/frontend/src/lib/contexts/ModalContext.tsx +35 -0
- package/frontend/src/lib/contexts/SocketContext.tsx +119 -75
- package/frontend/src/lib/hooks/useMetadata.ts +45 -1
- package/frontend/src/lib/hooks/useModal.tsx +2 -0
- package/frontend/src/lib/routing/AppRoutes.tsx +103 -84
- package/frontend/src/lib/services/metadata.service.ts +20 -3
- package/frontend/src/lib/utils/cloudMessaging.ts +1 -1
- package/frontend/src/lib/utils/menuItems.ts +223 -183
- package/frontend/src/lib/utils/utils.ts +196 -183
- package/frontend/tsconfig.json +25 -25
- package/frontend/tsconfig.node.json +9 -9
- package/functions/deno.json +24 -24
- package/functions/server.ts +6 -6
- package/functions/worker-template.js +1 -1
- package/i18n/README.ar.md +130 -130
- package/i18n/README.de.md +130 -130
- package/i18n/README.es.md +154 -154
- package/i18n/README.fr.md +134 -134
- package/i18n/README.hi.md +129 -129
- package/i18n/README.ja.md +174 -174
- package/i18n/README.ko.md +136 -136
- package/i18n/README.pt-BR.md +131 -131
- package/i18n/README.ru.md +129 -129
- package/i18n/README.zh-CN.md +133 -133
- package/openapi/ai.yaml +825 -715
- package/openapi/auth.yaml +1324 -1244
- package/openapi/email.yaml +158 -0
- package/openapi/functions.yaml +475 -475
- package/openapi/health.yaml +29 -29
- package/openapi/logs.yaml +221 -223
- package/openapi/metadata.yaml +175 -177
- package/openapi/realtime.yaml +699 -0
- package/openapi/records.yaml +381 -381
- package/openapi/secrets.yaml +370 -370
- package/openapi/storage.yaml +875 -875
- package/openapi/tables.yaml +462 -463
- package/package.json +97 -97
- package/shared-schemas/package.json +31 -31
- package/shared-schemas/src/ai-api.schema.ts +251 -143
- package/shared-schemas/src/ai.schema.ts +8 -4
- package/shared-schemas/src/auth-api.schema.ts +380 -339
- package/shared-schemas/src/auth.schema.ts +18 -11
- package/shared-schemas/src/cloud-events.schema.ts +26 -0
- package/shared-schemas/src/database-api.schema.ts +32 -1
- package/shared-schemas/src/database.schema.ts +39 -0
- package/shared-schemas/src/deployments-api.schema.ts +55 -0
- package/shared-schemas/src/deployments.schema.ts +30 -0
- package/shared-schemas/src/docs.schema.ts +32 -0
- package/shared-schemas/src/email-api.schema.ts +30 -0
- package/shared-schemas/src/functions-api.schema.ts +13 -4
- package/shared-schemas/src/functions.schema.ts +1 -1
- package/shared-schemas/src/index.ts +22 -14
- package/shared-schemas/src/metadata.schema.ts +39 -4
- package/shared-schemas/src/realtime-api.schema.ts +111 -0
- package/shared-schemas/src/realtime.schema.ts +143 -0
- package/shared-schemas/src/secrets-api.schema.ts +44 -0
- package/shared-schemas/src/secrets.schema.ts +15 -0
- package/shared-schemas/tsconfig.json +21 -21
- package/tsconfig.json +7 -7
- package/zeabur/README.md +26 -13
- package/zeabur/template.yml +1001 -1032
- package/.cursor/rules/cursor-rules.mdc +0 -94
- package/backend/src/types/profile.ts +0 -55
- package/frontend/src/components/ProjectInfoModal.tsx +0 -128
- package/frontend/src/features/database/hooks/useFullMetadata.ts +0 -18
- package/test-gemini.sh +0 -35
- package/test-usage-admin.sh +0 -57
- package/test-usage.sh +0 -50
- /package/frontend/src/features/auth/{page → pages}/ConfigurationPage.tsx +0 -0
- /package/frontend/src/features/database/{page → pages}/TemplatesPage.tsx +0 -0
- /package/frontend/src/features/login/{page → pages}/LoginPage.tsx +0 -0
- /package/frontend/src/features/logs/{page → pages}/AuditsPage.tsx +0 -0
- /package/frontend/src/features/logs/{page → pages}/MCPLogsPage.tsx +0 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
import { DatabaseManager } from '@/infra/database/database.manager.js';
|
|
3
|
+
import logger from '@/utils/logger.js';
|
|
4
|
+
import type { RealtimeMessage, RoleSchema } from '@insforge/shared-schemas';
|
|
5
|
+
import { RealtimeChannelService } from './realtime-channel.service.js';
|
|
6
|
+
import { RealtimeAuthService } from './realtime-auth.service.js';
|
|
7
|
+
|
|
8
|
+
export class RealtimeMessageService {
|
|
9
|
+
private static instance: RealtimeMessageService;
|
|
10
|
+
private pool: Pool | null = null;
|
|
11
|
+
|
|
12
|
+
private constructor() {}
|
|
13
|
+
|
|
14
|
+
static getInstance(): RealtimeMessageService {
|
|
15
|
+
if (!RealtimeMessageService.instance) {
|
|
16
|
+
RealtimeMessageService.instance = new RealtimeMessageService();
|
|
17
|
+
}
|
|
18
|
+
return RealtimeMessageService.instance;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private getPool(): Pool {
|
|
22
|
+
if (!this.pool) {
|
|
23
|
+
this.pool = DatabaseManager.getInstance().getPool();
|
|
24
|
+
}
|
|
25
|
+
return this.pool;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Insert a message into the channel (client-initiated send).
|
|
30
|
+
* RLS INSERT policy controls who can send to which channels.
|
|
31
|
+
* pg_notify is automatically triggered by database trigger on insert.
|
|
32
|
+
*
|
|
33
|
+
* @returns The inserted message data for broadcasting, or null if RLS denied the insert
|
|
34
|
+
*/
|
|
35
|
+
async insertMessage(
|
|
36
|
+
channelName: string,
|
|
37
|
+
eventName: string,
|
|
38
|
+
payload: Record<string, unknown>,
|
|
39
|
+
userId: string | undefined,
|
|
40
|
+
userRole: RoleSchema
|
|
41
|
+
): Promise<{
|
|
42
|
+
channelId: string;
|
|
43
|
+
channelName: string;
|
|
44
|
+
eventName: string;
|
|
45
|
+
payload: Record<string, unknown>;
|
|
46
|
+
senderId: string | null;
|
|
47
|
+
} | null> {
|
|
48
|
+
// Get channel info
|
|
49
|
+
const channelService = RealtimeChannelService.getInstance();
|
|
50
|
+
const channel = await channelService.getByName(channelName);
|
|
51
|
+
|
|
52
|
+
if (!channel) {
|
|
53
|
+
logger.debug('Channel not found for message insert', { channelName });
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const client = await this.getPool().connect();
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Begin transaction to ensure settings persist across queries
|
|
61
|
+
await client.query('BEGIN');
|
|
62
|
+
|
|
63
|
+
// Switch to specified role to enforce RLS policies
|
|
64
|
+
await client.query(`SET LOCAL ROLE ${userRole}`);
|
|
65
|
+
|
|
66
|
+
// Set user context for RLS policy evaluation
|
|
67
|
+
const authService = RealtimeAuthService.getInstance();
|
|
68
|
+
await authService.setUserContext(client, userId, channelName);
|
|
69
|
+
|
|
70
|
+
// Attempt INSERT with sender info - RLS will allow/deny based on policies
|
|
71
|
+
// No RETURNING clause needed - trigger handles pg_notify
|
|
72
|
+
await client.query(
|
|
73
|
+
`INSERT INTO realtime.messages (event_name, channel_id, channel_name, payload, sender_type, sender_id)
|
|
74
|
+
VALUES ($1, $2, $3, $4, 'user', $5)`,
|
|
75
|
+
[eventName, channel.id, channelName, JSON.stringify(payload), userId || null]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Commit transaction - insert succeeded
|
|
79
|
+
await client.query('COMMIT');
|
|
80
|
+
|
|
81
|
+
logger.debug('Client message inserted', {
|
|
82
|
+
channelName,
|
|
83
|
+
eventName,
|
|
84
|
+
userId,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
channelId: channel.id,
|
|
89
|
+
channelName,
|
|
90
|
+
eventName,
|
|
91
|
+
payload,
|
|
92
|
+
senderId: userId || null,
|
|
93
|
+
};
|
|
94
|
+
} catch (error) {
|
|
95
|
+
// Rollback transaction on error
|
|
96
|
+
await client.query('ROLLBACK').catch(() => {});
|
|
97
|
+
|
|
98
|
+
// RLS policy denied the INSERT or other error
|
|
99
|
+
logger.debug('Message insert denied or failed', { channelName, eventName, userId, error });
|
|
100
|
+
return null;
|
|
101
|
+
} finally {
|
|
102
|
+
// Reset role back to default before releasing connection
|
|
103
|
+
await client.query('RESET ROLE');
|
|
104
|
+
client.release();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get a message by ID (used by RealtimeManager after pg_notify)
|
|
110
|
+
*/
|
|
111
|
+
async getById(id: string): Promise<RealtimeMessage | null> {
|
|
112
|
+
const result = await this.getPool().query(
|
|
113
|
+
`SELECT
|
|
114
|
+
id,
|
|
115
|
+
event_name as "eventName",
|
|
116
|
+
channel_id as "channelId",
|
|
117
|
+
channel_name as "channelName",
|
|
118
|
+
payload,
|
|
119
|
+
sender_type as "senderType",
|
|
120
|
+
sender_id as "senderId",
|
|
121
|
+
ws_audience_count as "wsAudienceCount",
|
|
122
|
+
wh_audience_count as "whAudienceCount",
|
|
123
|
+
wh_delivered_count as "whDeliveredCount",
|
|
124
|
+
created_at as "createdAt"
|
|
125
|
+
FROM realtime.messages
|
|
126
|
+
WHERE id = $1`,
|
|
127
|
+
[id]
|
|
128
|
+
);
|
|
129
|
+
return result.rows[0] || null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async list(
|
|
133
|
+
options: {
|
|
134
|
+
channelId?: string;
|
|
135
|
+
eventName?: string;
|
|
136
|
+
limit?: number;
|
|
137
|
+
offset?: number;
|
|
138
|
+
} = {}
|
|
139
|
+
): Promise<RealtimeMessage[]> {
|
|
140
|
+
const { channelId, eventName, limit = 100, offset = 0 } = options;
|
|
141
|
+
|
|
142
|
+
let query = `
|
|
143
|
+
SELECT
|
|
144
|
+
id,
|
|
145
|
+
event_name as "eventName",
|
|
146
|
+
channel_id as "channelId",
|
|
147
|
+
channel_name as "channelName",
|
|
148
|
+
payload,
|
|
149
|
+
sender_type as "senderType",
|
|
150
|
+
sender_id as "senderId",
|
|
151
|
+
ws_audience_count as "wsAudienceCount",
|
|
152
|
+
wh_audience_count as "whAudienceCount",
|
|
153
|
+
wh_delivered_count as "whDeliveredCount",
|
|
154
|
+
created_at as "createdAt"
|
|
155
|
+
FROM realtime.messages
|
|
156
|
+
WHERE 1=1
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
const params: (string | number)[] = [];
|
|
160
|
+
let paramIndex = 1;
|
|
161
|
+
|
|
162
|
+
if (channelId) {
|
|
163
|
+
query += ` AND channel_id = $${paramIndex++}`;
|
|
164
|
+
params.push(channelId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (eventName) {
|
|
168
|
+
query += ` AND event_name = $${paramIndex++}`;
|
|
169
|
+
params.push(eventName);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
query += ` ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
|
|
173
|
+
params.push(limit, offset);
|
|
174
|
+
|
|
175
|
+
const result = await this.getPool().query(query, params);
|
|
176
|
+
return result.rows;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Update message record with delivery statistics
|
|
181
|
+
*/
|
|
182
|
+
async updateDeliveryStats(
|
|
183
|
+
messageId: string,
|
|
184
|
+
stats: {
|
|
185
|
+
wsAudienceCount: number;
|
|
186
|
+
whAudienceCount: number;
|
|
187
|
+
whDeliveredCount: number;
|
|
188
|
+
}
|
|
189
|
+
): Promise<void> {
|
|
190
|
+
await this.getPool().query(
|
|
191
|
+
`UPDATE realtime.messages
|
|
192
|
+
SET
|
|
193
|
+
ws_audience_count = $2,
|
|
194
|
+
wh_audience_count = $3,
|
|
195
|
+
wh_delivered_count = $4
|
|
196
|
+
WHERE id = $1`,
|
|
197
|
+
[messageId, stats.wsAudienceCount, stats.whAudienceCount, stats.whDeliveredCount]
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async getStats(
|
|
202
|
+
options: {
|
|
203
|
+
channelId?: string;
|
|
204
|
+
since?: Date;
|
|
205
|
+
} = {}
|
|
206
|
+
): Promise<{
|
|
207
|
+
totalMessages: number;
|
|
208
|
+
whDeliveryRate: number;
|
|
209
|
+
topEvents: { eventName: string; count: number }[];
|
|
210
|
+
}> {
|
|
211
|
+
const { channelId, since } = options;
|
|
212
|
+
|
|
213
|
+
let whereClause = '1=1';
|
|
214
|
+
const params: (string | Date)[] = [];
|
|
215
|
+
let paramIndex = 1;
|
|
216
|
+
|
|
217
|
+
if (channelId) {
|
|
218
|
+
whereClause += ` AND channel_id = $${paramIndex++}`;
|
|
219
|
+
params.push(channelId);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (since) {
|
|
223
|
+
whereClause += ` AND created_at >= $${paramIndex++}`;
|
|
224
|
+
params.push(since);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const statsResult = await this.getPool().query(
|
|
228
|
+
`SELECT
|
|
229
|
+
COUNT(*) as total_messages,
|
|
230
|
+
SUM(wh_audience_count) as wh_audience_total,
|
|
231
|
+
SUM(wh_delivered_count) as wh_delivered_total
|
|
232
|
+
FROM realtime.messages
|
|
233
|
+
WHERE ${whereClause}`,
|
|
234
|
+
params
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const topEventsResult = await this.getPool().query(
|
|
238
|
+
`SELECT event_name, COUNT(*) as count
|
|
239
|
+
FROM realtime.messages
|
|
240
|
+
WHERE ${whereClause}
|
|
241
|
+
GROUP BY event_name
|
|
242
|
+
ORDER BY count DESC
|
|
243
|
+
LIMIT 10`,
|
|
244
|
+
params
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const stats = statsResult.rows[0];
|
|
248
|
+
const whAudienceTotal = parseInt(stats.wh_audience_total) || 0;
|
|
249
|
+
const whDeliveredTotal = parseInt(stats.wh_delivered_total) || 0;
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
totalMessages: parseInt(stats.total_messages) || 0,
|
|
253
|
+
whDeliveryRate: whAudienceTotal > 0 ? whDeliveredTotal / whAudienceTotal : 0,
|
|
254
|
+
topEvents: topEventsResult.rows.map((row) => ({
|
|
255
|
+
eventName: row.event_name,
|
|
256
|
+
count: parseInt(row.count),
|
|
257
|
+
})),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -3,21 +3,9 @@ import crypto from 'crypto';
|
|
|
3
3
|
import { DatabaseManager } from '@/infra/database/database.manager.js';
|
|
4
4
|
import logger from '@/utils/logger.js';
|
|
5
5
|
import { EncryptionManager } from '@/infra/security/encryption.manager.js';
|
|
6
|
+
import { SecretSchema, CreateSecretRequest } from '@insforge/shared-schemas';
|
|
6
7
|
|
|
7
|
-
export interface
|
|
8
|
-
id: string;
|
|
9
|
-
key: string;
|
|
10
|
-
isActive: boolean;
|
|
11
|
-
isReserved: boolean;
|
|
12
|
-
lastUsedAt: Date | null;
|
|
13
|
-
expiresAt: Date | null;
|
|
14
|
-
createdAt: Date;
|
|
15
|
-
updatedAt: Date;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface CreateSecretInput {
|
|
19
|
-
key: string;
|
|
20
|
-
value: string;
|
|
8
|
+
export interface CreateSecretInput extends CreateSecretRequest {
|
|
21
9
|
isReserved?: boolean;
|
|
22
10
|
expiresAt?: Date;
|
|
23
11
|
}
|
|
@@ -59,7 +47,7 @@ export class SecretService {
|
|
|
59
47
|
const encryptedValue = EncryptionManager.encrypt(input.value);
|
|
60
48
|
|
|
61
49
|
const result = await this.getPool().query(
|
|
62
|
-
`INSERT INTO
|
|
50
|
+
`INSERT INTO system.secrets (key, value_ciphertext, is_reserved, expires_at)
|
|
63
51
|
VALUES ($1, $2, $3, $4)
|
|
64
52
|
RETURNING id`,
|
|
65
53
|
[input.key, encryptedValue, input.isReserved || false, input.expiresAt || null]
|
|
@@ -79,7 +67,7 @@ export class SecretService {
|
|
|
79
67
|
async getSecretById(id: string): Promise<string | null> {
|
|
80
68
|
try {
|
|
81
69
|
const result = await this.getPool().query(
|
|
82
|
-
`UPDATE
|
|
70
|
+
`UPDATE system.secrets
|
|
83
71
|
SET last_used_at = NOW()
|
|
84
72
|
WHERE id = $1 AND is_active = true
|
|
85
73
|
AND (expires_at IS NULL OR expires_at > NOW())
|
|
@@ -106,7 +94,7 @@ export class SecretService {
|
|
|
106
94
|
async getSecretByKey(key: string): Promise<string | null> {
|
|
107
95
|
try {
|
|
108
96
|
const result = await this.getPool().query(
|
|
109
|
-
`UPDATE
|
|
97
|
+
`UPDATE system.secrets
|
|
110
98
|
SET last_used_at = NOW()
|
|
111
99
|
WHERE key = $1 AND is_active = true
|
|
112
100
|
AND (expires_at IS NULL OR expires_at > NOW())
|
|
@@ -142,7 +130,7 @@ export class SecretService {
|
|
|
142
130
|
expires_at as "expiresAt",
|
|
143
131
|
created_at as "createdAt",
|
|
144
132
|
updated_at as "updatedAt"
|
|
145
|
-
FROM
|
|
133
|
+
FROM system.secrets
|
|
146
134
|
ORDER BY created_at DESC`
|
|
147
135
|
);
|
|
148
136
|
|
|
@@ -183,10 +171,14 @@ export class SecretService {
|
|
|
183
171
|
values.push(input.expiresAt);
|
|
184
172
|
}
|
|
185
173
|
|
|
174
|
+
if (updates.length === 0) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
186
178
|
values.push(id);
|
|
187
179
|
|
|
188
180
|
const result = await this.getPool().query(
|
|
189
|
-
`UPDATE
|
|
181
|
+
`UPDATE system.secrets
|
|
190
182
|
SET ${updates.join(', ')}
|
|
191
183
|
WHERE id = $${paramCount}`,
|
|
192
184
|
values
|
|
@@ -203,6 +195,81 @@ export class SecretService {
|
|
|
203
195
|
}
|
|
204
196
|
}
|
|
205
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Update a secret by key
|
|
200
|
+
*/
|
|
201
|
+
async updateSecretByKey(key: string, input: UpdateSecretInput): Promise<boolean> {
|
|
202
|
+
try {
|
|
203
|
+
const updates: string[] = [];
|
|
204
|
+
const values: (string | boolean | Date | null)[] = [];
|
|
205
|
+
let paramCount = 1;
|
|
206
|
+
|
|
207
|
+
if (input.value !== undefined) {
|
|
208
|
+
const encryptedValue = EncryptionManager.encrypt(input.value);
|
|
209
|
+
updates.push(`value_ciphertext = $${paramCount++}`);
|
|
210
|
+
values.push(encryptedValue);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (input.isActive !== undefined) {
|
|
214
|
+
updates.push(`is_active = $${paramCount++}`);
|
|
215
|
+
values.push(input.isActive);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (input.isReserved !== undefined) {
|
|
219
|
+
updates.push(`is_reserved = $${paramCount++}`);
|
|
220
|
+
values.push(input.isReserved);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (input.expiresAt !== undefined) {
|
|
224
|
+
updates.push(`expires_at = $${paramCount++}`);
|
|
225
|
+
values.push(input.expiresAt);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (updates.length === 0) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
values.push(key);
|
|
233
|
+
|
|
234
|
+
const result = await this.getPool().query(
|
|
235
|
+
`UPDATE system.secrets
|
|
236
|
+
SET ${updates.join(', ')}
|
|
237
|
+
WHERE key = $${paramCount}`,
|
|
238
|
+
values
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const success = (result.rowCount ?? 0) > 0;
|
|
242
|
+
if (success) {
|
|
243
|
+
logger.info('Secret updated by key', { key });
|
|
244
|
+
}
|
|
245
|
+
return success;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
logger.error('Failed to update secret by key', { error, key });
|
|
248
|
+
throw new Error('Failed to update secret');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Delete a secret by key
|
|
254
|
+
*/
|
|
255
|
+
async deleteSecretByKey(key: string): Promise<boolean> {
|
|
256
|
+
try {
|
|
257
|
+
const result = await this.getPool().query(
|
|
258
|
+
'DELETE FROM system.secrets WHERE key = $1 AND is_reserved = false',
|
|
259
|
+
[key]
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const success = (result.rowCount ?? 0) > 0;
|
|
263
|
+
if (success) {
|
|
264
|
+
logger.info('Secret deleted by key', { key });
|
|
265
|
+
}
|
|
266
|
+
return success;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
logger.error('Failed to delete secret by key', { error, key });
|
|
269
|
+
throw new Error('Failed to delete secret');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
206
273
|
/**
|
|
207
274
|
* Check if a secret value matches the stored value
|
|
208
275
|
*/
|
|
@@ -210,7 +277,7 @@ export class SecretService {
|
|
|
210
277
|
try {
|
|
211
278
|
// Optimized: Single query that retrieves and updates in one operation
|
|
212
279
|
const result = await this.getPool().query(
|
|
213
|
-
`UPDATE
|
|
280
|
+
`UPDATE system.secrets
|
|
214
281
|
SET last_used_at = NOW()
|
|
215
282
|
WHERE key = $1
|
|
216
283
|
AND is_active = true
|
|
@@ -225,7 +292,12 @@ export class SecretService {
|
|
|
225
292
|
}
|
|
226
293
|
|
|
227
294
|
const decryptedValue = EncryptionManager.decrypt(result.rows[0].value_ciphertext);
|
|
228
|
-
|
|
295
|
+
// Use constant-time comparison to prevent timing attacks
|
|
296
|
+
const decryptedBuffer = Buffer.from(decryptedValue);
|
|
297
|
+
const valueBuffer = Buffer.from(value);
|
|
298
|
+
const matches =
|
|
299
|
+
decryptedBuffer.length === valueBuffer.length &&
|
|
300
|
+
crypto.timingSafeEqual(decryptedBuffer, valueBuffer);
|
|
229
301
|
|
|
230
302
|
if (matches) {
|
|
231
303
|
logger.info('Secret check successful', { key });
|
|
@@ -247,7 +319,7 @@ export class SecretService {
|
|
|
247
319
|
try {
|
|
248
320
|
// Optimized: Single query with WHERE clause to prevent deleting reserved secrets
|
|
249
321
|
const result = await this.getPool().query(
|
|
250
|
-
'DELETE FROM
|
|
322
|
+
'DELETE FROM system.secrets WHERE id = $1 AND is_reserved = false',
|
|
251
323
|
[id]
|
|
252
324
|
);
|
|
253
325
|
|
|
@@ -257,7 +329,7 @@ export class SecretService {
|
|
|
257
329
|
} else {
|
|
258
330
|
// Check if it exists but is reserved
|
|
259
331
|
const checkResult = await this.getPool().query(
|
|
260
|
-
'SELECT is_reserved FROM
|
|
332
|
+
'SELECT is_reserved FROM system.secrets WHERE id = $1',
|
|
261
333
|
[id]
|
|
262
334
|
);
|
|
263
335
|
if (checkResult.rows.length && checkResult.rows[0].is_reserved) {
|
|
@@ -279,7 +351,9 @@ export class SecretService {
|
|
|
279
351
|
try {
|
|
280
352
|
await client.query('BEGIN');
|
|
281
353
|
|
|
282
|
-
const oldSecretResult = await client.query(`SELECT key FROM
|
|
354
|
+
const oldSecretResult = await client.query(`SELECT key FROM system.secrets WHERE id = $1`, [
|
|
355
|
+
id,
|
|
356
|
+
]);
|
|
283
357
|
|
|
284
358
|
if (!oldSecretResult.rows.length) {
|
|
285
359
|
throw new Error('Secret not found');
|
|
@@ -288,7 +362,7 @@ export class SecretService {
|
|
|
288
362
|
const secretKey = oldSecretResult.rows[0].key;
|
|
289
363
|
|
|
290
364
|
await client.query(
|
|
291
|
-
`UPDATE
|
|
365
|
+
`UPDATE system.secrets
|
|
292
366
|
SET is_active = false,
|
|
293
367
|
expires_at = NOW() + INTERVAL '24 hours'
|
|
294
368
|
WHERE id = $1`,
|
|
@@ -297,7 +371,7 @@ export class SecretService {
|
|
|
297
371
|
|
|
298
372
|
const encryptedValue = EncryptionManager.encrypt(newValue);
|
|
299
373
|
const newSecretResult = await client.query(
|
|
300
|
-
`INSERT INTO
|
|
374
|
+
`INSERT INTO system.secrets (key, value_ciphertext)
|
|
301
375
|
VALUES ($1, $2)
|
|
302
376
|
RETURNING id`,
|
|
303
377
|
[secretKey, encryptedValue]
|
|
@@ -327,7 +401,7 @@ export class SecretService {
|
|
|
327
401
|
async cleanupExpiredSecrets(): Promise<number> {
|
|
328
402
|
try {
|
|
329
403
|
const result = await this.getPool().query(
|
|
330
|
-
`DELETE FROM
|
|
404
|
+
`DELETE FROM system.secrets
|
|
331
405
|
WHERE expires_at IS NOT NULL
|
|
332
406
|
AND expires_at < NOW()
|
|
333
407
|
RETURNING id`
|
|
@@ -109,7 +109,7 @@ export class StorageService {
|
|
|
109
109
|
// This query finds all files matching the pattern and extracts the counter number
|
|
110
110
|
const result = await this.getPool().query(
|
|
111
111
|
`
|
|
112
|
-
SELECT key FROM
|
|
112
|
+
SELECT key FROM storage.objects
|
|
113
113
|
WHERE bucket = $1
|
|
114
114
|
AND (key = $2 OR key LIKE $3)
|
|
115
115
|
`,
|
|
@@ -169,7 +169,7 @@ export class StorageService {
|
|
|
169
169
|
// Save metadata to database and return the timestamp in one operation
|
|
170
170
|
const result = await client.query(
|
|
171
171
|
`
|
|
172
|
-
INSERT INTO
|
|
172
|
+
INSERT INTO storage.objects (bucket, key, size, mime_type, uploaded_by)
|
|
173
173
|
VALUES ($1, $2, $3, $4, $5)
|
|
174
174
|
RETURNING uploaded_at as "uploadedAt"
|
|
175
175
|
`,
|
|
@@ -207,7 +207,7 @@ export class StorageService {
|
|
|
207
207
|
this.validateKey(key);
|
|
208
208
|
|
|
209
209
|
const result = await this.getPool().query(
|
|
210
|
-
'SELECT * FROM
|
|
210
|
+
'SELECT * FROM storage.objects WHERE bucket = $1 AND key = $2',
|
|
211
211
|
[bucket, key]
|
|
212
212
|
);
|
|
213
213
|
|
|
@@ -249,7 +249,7 @@ export class StorageService {
|
|
|
249
249
|
// Check permissions
|
|
250
250
|
if (!isAdmin) {
|
|
251
251
|
const fileResult = await client.query(
|
|
252
|
-
'SELECT uploaded_by FROM
|
|
252
|
+
'SELECT uploaded_by FROM storage.objects WHERE bucket = $1 AND key = $2',
|
|
253
253
|
[bucket, key]
|
|
254
254
|
);
|
|
255
255
|
|
|
@@ -273,10 +273,10 @@ export class StorageService {
|
|
|
273
273
|
await this.provider.deleteObject(bucket, key);
|
|
274
274
|
|
|
275
275
|
// Delete from database
|
|
276
|
-
const result = await client.query(
|
|
277
|
-
bucket,
|
|
278
|
-
key
|
|
279
|
-
|
|
276
|
+
const result = await client.query(
|
|
277
|
+
'DELETE FROM storage.objects WHERE bucket = $1 AND key = $2',
|
|
278
|
+
[bucket, key]
|
|
279
|
+
);
|
|
280
280
|
|
|
281
281
|
return result.rowCount !== null && result.rowCount > 0;
|
|
282
282
|
} finally {
|
|
@@ -295,8 +295,8 @@ export class StorageService {
|
|
|
295
295
|
|
|
296
296
|
const client = await this.getPool().connect();
|
|
297
297
|
try {
|
|
298
|
-
let query = 'SELECT * FROM
|
|
299
|
-
let countQuery = 'SELECT COUNT(*) as count FROM
|
|
298
|
+
let query = 'SELECT * FROM storage.objects WHERE bucket = $1';
|
|
299
|
+
let countQuery = 'SELECT COUNT(*) as count FROM storage.objects WHERE bucket = $1';
|
|
300
300
|
const params: (string | number)[] = [bucket];
|
|
301
301
|
let paramIndex = 2;
|
|
302
302
|
|
|
@@ -338,7 +338,7 @@ export class StorageService {
|
|
|
338
338
|
|
|
339
339
|
async isBucketPublic(bucket: string): Promise<boolean> {
|
|
340
340
|
const result = await this.getPool().query(
|
|
341
|
-
'SELECT public FROM
|
|
341
|
+
'SELECT public FROM storage.buckets WHERE name = $1',
|
|
342
342
|
[bucket]
|
|
343
343
|
);
|
|
344
344
|
return result.rows[0]?.public || false;
|
|
@@ -348,7 +348,7 @@ export class StorageService {
|
|
|
348
348
|
const client = await this.getPool().connect();
|
|
349
349
|
try {
|
|
350
350
|
// Check if bucket exists
|
|
351
|
-
const bucketResult = await client.query('SELECT name FROM
|
|
351
|
+
const bucketResult = await client.query('SELECT name FROM storage.buckets WHERE name = $1', [
|
|
352
352
|
bucket,
|
|
353
353
|
]);
|
|
354
354
|
|
|
@@ -356,9 +356,9 @@ export class StorageService {
|
|
|
356
356
|
throw new Error(`Bucket "${bucket}" does not exist`);
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
-
// Update bucket visibility in
|
|
359
|
+
// Update bucket visibility in storage.buckets table
|
|
360
360
|
await client.query(
|
|
361
|
-
'UPDATE
|
|
361
|
+
'UPDATE storage.buckets SET public = $1, updated_at = CURRENT_TIMESTAMP WHERE name = $2',
|
|
362
362
|
[isPublic, bucket]
|
|
363
363
|
);
|
|
364
364
|
|
|
@@ -370,9 +370,9 @@ export class StorageService {
|
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
async listBuckets(): Promise<StorageBucketSchema[]> {
|
|
373
|
-
// Get all buckets with their metadata from
|
|
373
|
+
// Get all buckets with their metadata from storage.buckets table
|
|
374
374
|
const result = await this.getPool().query(
|
|
375
|
-
'SELECT name, public, created_at as "createdAt" FROM
|
|
375
|
+
'SELECT name, public, created_at as "createdAt" FROM storage.buckets ORDER BY name'
|
|
376
376
|
);
|
|
377
377
|
|
|
378
378
|
return result.rows as StorageBucketSchema[];
|
|
@@ -384,7 +384,7 @@ export class StorageService {
|
|
|
384
384
|
const client = await this.getPool().connect();
|
|
385
385
|
try {
|
|
386
386
|
// Check if bucket already exists
|
|
387
|
-
const existing = await client.query('SELECT name FROM
|
|
387
|
+
const existing = await client.query('SELECT name FROM storage.buckets WHERE name = $1', [
|
|
388
388
|
bucket,
|
|
389
389
|
]);
|
|
390
390
|
|
|
@@ -392,8 +392,8 @@ export class StorageService {
|
|
|
392
392
|
throw new Error(`Bucket "${bucket}" already exists`);
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
-
// Insert bucket into
|
|
396
|
-
await client.query('INSERT INTO
|
|
395
|
+
// Insert bucket into storage.buckets table
|
|
396
|
+
await client.query('INSERT INTO storage.buckets (name, public) VALUES ($1, $2)', [
|
|
397
397
|
bucket,
|
|
398
398
|
isPublic,
|
|
399
399
|
]);
|
|
@@ -414,7 +414,7 @@ export class StorageService {
|
|
|
414
414
|
const client = await this.getPool().connect();
|
|
415
415
|
try {
|
|
416
416
|
// Check if bucket exists
|
|
417
|
-
const bucketResult = await client.query('SELECT name FROM
|
|
417
|
+
const bucketResult = await client.query('SELECT name FROM storage.buckets WHERE name = $1', [
|
|
418
418
|
bucket,
|
|
419
419
|
]);
|
|
420
420
|
|
|
@@ -425,8 +425,8 @@ export class StorageService {
|
|
|
425
425
|
// Delete bucket using backend (handles all files)
|
|
426
426
|
await this.provider.deleteBucket(bucket);
|
|
427
427
|
|
|
428
|
-
// Delete from storage table (cascade will handle
|
|
429
|
-
await client.query('DELETE FROM
|
|
428
|
+
// Delete from storage table (cascade will handle storage.objects entries)
|
|
429
|
+
await client.query('DELETE FROM storage.buckets WHERE name = $1', [bucket]);
|
|
430
430
|
|
|
431
431
|
// Update storage metadata
|
|
432
432
|
// Metadata is now updated on-demand
|
|
@@ -451,7 +451,7 @@ export class StorageService {
|
|
|
451
451
|
const client = await this.getPool().connect();
|
|
452
452
|
try {
|
|
453
453
|
// Check if bucket exists
|
|
454
|
-
const bucketResult = await client.query('SELECT name FROM
|
|
454
|
+
const bucketResult = await client.query('SELECT name FROM storage.buckets WHERE name = $1', [
|
|
455
455
|
bucket,
|
|
456
456
|
]);
|
|
457
457
|
|
|
@@ -503,7 +503,7 @@ export class StorageService {
|
|
|
503
503
|
try {
|
|
504
504
|
// Check if already confirmed
|
|
505
505
|
const existingResult = await client.query(
|
|
506
|
-
'SELECT key FROM
|
|
506
|
+
'SELECT key FROM storage.objects WHERE bucket = $1 AND key = $2',
|
|
507
507
|
[bucket, key]
|
|
508
508
|
);
|
|
509
509
|
|
|
@@ -514,7 +514,7 @@ export class StorageService {
|
|
|
514
514
|
// Save metadata to database and return the timestamp in one operation
|
|
515
515
|
const result = await client.query(
|
|
516
516
|
`
|
|
517
|
-
INSERT INTO
|
|
517
|
+
INSERT INTO storage.objects (bucket, key, size, mime_type, uploaded_by)
|
|
518
518
|
VALUES ($1, $2, $3, $4, $5)
|
|
519
519
|
RETURNING uploaded_at as "uploadedAt"
|
|
520
520
|
`,
|
|
@@ -548,9 +548,9 @@ export class StorageService {
|
|
|
548
548
|
* Get storage metadata
|
|
549
549
|
*/
|
|
550
550
|
async getMetadata(): Promise<StorageMetadataSchema> {
|
|
551
|
-
// Get storage buckets from
|
|
551
|
+
// Get storage buckets from storage.buckets table
|
|
552
552
|
const result = await this.getPool().query(
|
|
553
|
-
'SELECT name, public, created_at as "createdAt" FROM
|
|
553
|
+
'SELECT name, public, created_at as "createdAt" FROM storage.buckets ORDER BY name'
|
|
554
554
|
);
|
|
555
555
|
|
|
556
556
|
const storageBuckets = result.rows as StorageBucketSchema[];
|
|
@@ -572,7 +572,7 @@ export class StorageService {
|
|
|
572
572
|
try {
|
|
573
573
|
// Query to get object count for each bucket
|
|
574
574
|
const result = await this.getPool().query(
|
|
575
|
-
'SELECT bucket, COUNT(*) as count FROM
|
|
575
|
+
'SELECT bucket, COUNT(*) as count FROM storage.objects GROUP BY bucket'
|
|
576
576
|
);
|
|
577
577
|
|
|
578
578
|
const bucketCounts = result.rows as { bucket: string; count: string }[];
|
|
@@ -595,11 +595,11 @@ export class StorageService {
|
|
|
595
595
|
|
|
596
596
|
private async getStorageSizeInGB(): Promise<number> {
|
|
597
597
|
try {
|
|
598
|
-
// Query the
|
|
598
|
+
// Query the storage.objects table to sum all file sizes
|
|
599
599
|
const result = await this.getPool().query(
|
|
600
600
|
`
|
|
601
601
|
SELECT COALESCE(SUM(size), 0) as total_size
|
|
602
|
-
FROM
|
|
602
|
+
FROM storage.objects
|
|
603
603
|
`
|
|
604
604
|
);
|
|
605
605
|
|