insforge 0.3.3 → 1.3.0
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 -0
- package/.dockerignore +60 -57
- package/.env.example +84 -49
- package/.github/ISSUE_TEMPLATE/bug_report.yml +36 -83
- package/.github/ISSUE_TEMPLATE/config.yml +11 -11
- package/.github/ISSUE_TEMPLATE/feature_request.yml +26 -79
- package/.github/PULL_REQUEST_TEMPLATE.md +7 -0
- package/.github/copilot-instructions.md +146 -146
- package/.github/workflows/build-image.yml +66 -65
- package/.github/workflows/ci-premerge-check.yml +23 -23
- package/.github/workflows/e2e.yml +63 -0
- package/.github/workflows/lint-and-format.yml +32 -32
- package/.prettierignore +64 -64
- package/CHANGELOG.md +44 -3
- package/CLAUDE_PLUGIN.md +104 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +125 -125
- package/Dockerfile +30 -27
- package/GITHUB_OAUTH_SETUP.md +49 -49
- package/GOOGLE_OAUTH_SETUP.md +148 -148
- package/LICENSE +201 -201
- package/README.md +182 -134
- package/assets/Dark.svg +23 -23
- package/assets/mcpInstallv2.png +0 -0
- package/assets/sampleResponse.png +0 -0
- package/auth/index.html +13 -0
- package/auth/package.json +28 -0
- package/auth/public/favicon.ico +0 -0
- package/auth/src/App.tsx +33 -0
- package/auth/src/components/ErrorCard.tsx +37 -0
- package/auth/src/components/Layout.tsx +13 -0
- package/auth/src/index.css +19 -0
- package/auth/src/lib/broadcastService.ts +117 -0
- package/auth/src/lib/utils.ts +11 -0
- package/auth/src/main.tsx +22 -0
- package/auth/src/pages/ForgotPasswordPage.tsx +11 -0
- package/auth/src/pages/ResetPasswordPage.tsx +11 -0
- package/auth/src/pages/SignInPage.tsx +60 -0
- package/auth/src/pages/SignUpPage.tsx +60 -0
- package/auth/src/pages/VerifyEmailPage.tsx +20 -0
- package/auth/src/vite-env.d.ts +10 -0
- package/auth/tsconfig.json +32 -0
- package/auth/tsconfig.node.json +11 -0
- package/auth/vite.config.ts +25 -0
- package/backend/package.json +78 -75
- package/backend/src/api/{middleware → middlewares}/auth.ts +8 -9
- package/backend/src/api/middlewares/rate-limiters.ts +127 -0
- package/backend/src/api/routes/{ai.ts → ai/index.routes.ts} +22 -26
- package/backend/src/api/routes/auth/index.routes.ts +667 -0
- package/backend/src/api/routes/auth/oauth.routes.ts +473 -0
- package/backend/src/api/routes/{database.advance.ts → database/advance.routes.ts} +128 -65
- package/backend/src/api/routes/database/index.routes.ts +90 -0
- package/backend/src/api/routes/{database.records.ts → database/records.routes.ts} +26 -12
- package/backend/src/api/routes/{database.tables.ts → database/tables.routes.ts} +6 -23
- package/backend/src/api/routes/docs/index.routes.ts +75 -0
- package/backend/src/api/routes/email/index.routes.ts +35 -0
- package/backend/src/api/routes/functions/index.routes.ts +194 -0
- package/backend/src/api/routes/{logs.ts → logs/index.routes.ts} +25 -30
- package/backend/src/api/routes/{metadata.ts → metadata/index.routes.ts} +33 -31
- 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/{secrets.ts → secrets/index.routes.ts} +27 -22
- package/backend/src/api/routes/{storage.ts → storage/index.routes.ts} +48 -61
- package/backend/src/api/routes/usage/index.routes.ts +91 -0
- package/backend/src/infra/config/app.config.ts +51 -0
- package/backend/src/infra/database/database.manager.ts +182 -0
- package/backend/{migrations → src/infra/database/migrations}/000_create-base-tables.sql +141 -141
- package/backend/{migrations → src/infra/database/migrations}/001_create-helper-functions.sql +40 -40
- package/backend/{migrations → src/infra/database/migrations}/002_rename-auth-tables.sql +29 -29
- package/backend/{migrations → src/infra/database/migrations}/003_create-users-table.sql +55 -55
- package/backend/{migrations → src/infra/database/migrations}/004_add-reload-postgrest-func.sql +23 -23
- package/backend/{migrations → src/infra/database/migrations}/005_enable-project-admin-modify-users.sql +29 -29
- package/backend/{migrations → src/infra/database/migrations}/006_modify-ai-usage-table.sql +24 -24
- package/backend/{migrations → src/infra/database/migrations}/007_drop-metadata-table.sql +1 -1
- package/backend/{migrations → src/infra/database/migrations}/008_add-system-tables.sql +76 -76
- package/backend/{migrations → src/infra/database/migrations}/009_add-function-secrets.sql +23 -23
- package/backend/{migrations → src/infra/database/migrations}/010_modify-ai-config-modalities.sql +93 -93
- package/backend/{migrations → src/infra/database/migrations}/011_refactor-secrets-table.sql +15 -15
- package/backend/{migrations → 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 -0
- package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +8 -0
- package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +60 -0
- package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -0
- package/backend/src/infra/database/migrations/017_create-realtime-schema.sql +233 -0
- package/backend/src/infra/realtime/realtime.manager.ts +246 -0
- package/backend/src/infra/realtime/webhook-sender.ts +82 -0
- package/backend/src/{core/secrets/encryption.ts → infra/security/encryption.manager.ts} +3 -2
- package/backend/src/infra/security/token.manager.ts +219 -0
- package/backend/src/infra/socket/socket.manager.ts +522 -0
- package/backend/src/providers/ai/openrouter.provider.ts +380 -0
- package/backend/src/providers/email/base.provider.ts +38 -0
- package/backend/src/providers/email/cloud.provider.ts +271 -0
- package/backend/src/{core/logs/providers → providers/logs}/base.provider.ts +11 -11
- package/backend/src/{core/logs/providers → providers/logs}/cloudwatch.provider.ts +61 -38
- package/backend/src/providers/logs/local.provider.ts +185 -0
- package/backend/src/providers/oauth/apple.provider.ts +266 -0
- package/backend/src/providers/oauth/base.provider.ts +29 -0
- package/backend/src/providers/oauth/discord.provider.ts +195 -0
- package/backend/src/providers/oauth/facebook.provider.ts +194 -0
- package/backend/src/providers/oauth/github.provider.ts +208 -0
- package/backend/src/providers/oauth/google.provider.ts +249 -0
- package/backend/src/providers/oauth/index.ts +8 -0
- package/backend/src/providers/oauth/linkedin.provider.ts +240 -0
- package/backend/src/providers/oauth/microsoft.provider.ts +169 -0
- package/backend/src/providers/oauth/x.provider.ts +202 -0
- package/backend/src/providers/storage/base.provider.ts +29 -0
- package/backend/src/providers/storage/local.provider.ts +103 -0
- package/backend/src/providers/storage/s3.provider.ts +313 -0
- package/backend/src/server.ts +317 -288
- package/backend/src/{core/ai/config.ts → services/ai/ai-config.service.ts} +19 -24
- package/backend/src/services/ai/ai-model.service.ts +60 -0
- package/backend/src/{core/ai/usage.ts → services/ai/ai-usage.service.ts} +28 -35
- package/backend/src/{core/ai/chat.ts → services/ai/chat-completion.service.ts} +37 -24
- package/backend/src/services/ai/helpers.ts +64 -0
- package/backend/src/{core/ai/image.ts → services/ai/image-generation.service.ts} +17 -19
- package/backend/src/services/ai/index.ts +13 -0
- package/backend/src/services/auth/auth-config.service.ts +250 -0
- package/backend/src/services/auth/auth-otp.service.ts +424 -0
- package/backend/src/services/auth/auth.service.ts +1150 -0
- package/backend/src/services/auth/index.ts +4 -0
- package/backend/src/{core/auth/oauth.ts → services/auth/oauth-config.service.ts} +106 -52
- package/backend/src/{core/database/advance.ts → services/database/database-advance.service.ts} +97 -131
- package/backend/src/services/database/database-table.service.ts +802 -0
- package/backend/src/services/database/database.service.ts +127 -0
- package/backend/src/services/email/email.service.ts +73 -0
- package/backend/src/{core/functions/functions.ts → services/functions/function.service.ts} +95 -88
- package/backend/src/{core/logs/audit.ts → services/logs/audit.service.ts} +92 -75
- package/backend/src/services/logs/log.service.ts +73 -0
- 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/{core/secrets/secrets.ts → services/secrets/secret.service.ts} +48 -66
- package/backend/src/services/storage/storage.service.ts +617 -0
- package/backend/src/services/usage/usage.service.ts +149 -0
- package/backend/src/types/auth.ts +77 -2
- package/backend/src/types/email.ts +8 -0
- package/backend/src/types/error-constants.ts +4 -0
- package/backend/src/types/logs.ts +0 -29
- package/backend/src/types/realtime.ts +18 -0
- package/backend/src/{core/socket/types.ts → types/socket.ts} +11 -36
- package/backend/src/utils/cookies.ts +35 -0
- package/backend/src/utils/environment.ts +9 -3
- package/backend/src/utils/logger.ts +20 -2
- package/backend/src/utils/s3-config-loader.ts +64 -0
- package/backend/src/utils/seed.ts +301 -205
- package/backend/src/utils/sql-parser.ts +91 -1
- package/backend/src/utils/utils.ts +114 -0
- package/backend/src/utils/validations.ts +40 -4
- 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 -0
- package/backend/tests/local/test-ai-usage.sh +80 -0
- 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 -0
- package/backend/tests/local/test-id-field.sh +200 -200
- package/backend/tests/local/test-logs.sh +132 -0
- package/backend/tests/local/test-public-bucket.sh +264 -264
- package/backend/tests/local/test-secrets.sh +249 -247
- 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-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 -0
- 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 -302
- package/backend/tests/unit/analyze-query.test.ts +697 -0
- package/backend/tests/unit/cloud-token.test.ts +48 -0
- package/backend/tests/unit/constant.test.ts +8 -0
- package/backend/tests/unit/email.test.ts +372 -0
- package/backend/tests/unit/environment.test.ts +59 -0
- package/backend/tests/unit/helpers.test.ts +63 -0
- package/backend/tests/unit/logger.test.ts +22 -0
- package/backend/tests/unit/rate-limit.test.ts +154 -0
- package/backend/tests/unit/response.test.ts +58 -0
- package/backend/tests/unit/sql-parser.test.ts +74 -0
- package/backend/tests/unit/uuid.test.ts +21 -0
- package/backend/tests/unit/validations.test.ts +80 -0
- package/backend/tsconfig.json +22 -22
- package/backend/vitest.config.ts +11 -0
- package/claude-plugin/.claude-plugin/plugin.json +24 -0
- package/claude-plugin/README.md +133 -0
- package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +270 -0
- package/docker-compose.prod.yml +204 -144
- package/docker-compose.yml +232 -167
- package/docker-init/db/db-init.sql +97 -125
- package/docker-init/db/jwt.sql +5 -5
- package/docker-init/db/postgresql.conf +16 -16
- package/docker-init/logs/vector.yml +236 -0
- package/docs/README.md +44 -0
- package/docs/agent-docs/real-time.md +269 -0
- package/docs/changelog.mdx +119 -0
- package/docs/core-concepts/ai/architecture.mdx +373 -0
- package/docs/core-concepts/ai/sdk.mdx +213 -0
- package/docs/core-concepts/authentication/architecture.mdx +278 -0
- package/docs/core-concepts/authentication/sdk.mdx +414 -0
- package/docs/core-concepts/authentication/ui-components/customization.mdx +529 -0
- package/docs/core-concepts/authentication/ui-components/nextjs.mdx +221 -0
- package/docs/core-concepts/authentication/ui-components/react-router.mdx +184 -0
- package/docs/core-concepts/authentication/ui-components/react.mdx +129 -0
- package/docs/core-concepts/database/architecture.mdx +256 -0
- package/docs/core-concepts/database/sdk.mdx +382 -0
- package/docs/core-concepts/email/architecture.mdx +101 -0
- package/docs/core-concepts/email/sdk.mdx +53 -0
- package/docs/core-concepts/functions/architecture.mdx +105 -0
- package/docs/core-concepts/functions/sdk.mdx +184 -0
- 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 -0
- package/docs/core-concepts/storage/sdk.mdx +253 -0
- package/docs/deployment/README.md +94 -0
- package/docs/deployment/deploy-to-aws-ec2.md +565 -0
- package/docs/deployment/deploy-to-azure-virtual-machines.md +313 -0
- package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -0
- package/docs/deployment/deploy-to-render.md +441 -0
- 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 +232 -0
- package/docs/examples/framework-guides/nextjs.mdx +131 -0
- package/docs/examples/framework-guides/nuxt.mdx +165 -0
- package/docs/examples/framework-guides/react.mdx +165 -0
- package/docs/examples/framework-guides/svelte.mdx +153 -0
- package/docs/examples/framework-guides/vue.mdx +159 -0
- package/docs/examples/overview.mdx +67 -0
- package/docs/favicon.svg +19 -0
- 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/mcp-installer.png +0 -0
- package/docs/images/changelog/dec-2025/realtime-module.jpg +0 -0
- package/docs/images/changelog/nov-2025/auth-components.webp +0 -0
- package/docs/images/changelog/nov-2025/database-metadata.webp +0 -0
- package/docs/images/changelog/nov-2025/quickstart-prompts.webp +0 -0
- package/docs/images/changelog/nov-2025/sql-editor.webp +0 -0
- package/docs/images/changelog/nov-2025/usage-page.webp +0 -0
- package/docs/images/changelog/october-2025/csv-upload.webp +0 -0
- package/docs/images/changelog/october-2025/logs-feature.webp +0 -0
- package/docs/images/changelog/october-2025/oauth-providers.webp +0 -0
- package/docs/images/checks-passed.png +0 -0
- package/docs/images/dashboard-connect-expanded.png +0 -0
- package/docs/images/dashboard-connect.png +0 -0
- package/docs/images/hero-dark.png +0 -0
- package/docs/images/hero-light.png +0 -0
- package/docs/images/icons/ai.svg +4 -0
- package/docs/images/icons/auth.svg +1 -0
- package/docs/images/icons/database.svg +1 -0
- package/docs/images/icons/function.svg +1 -0
- package/docs/images/icons/storage.svg +1 -0
- package/docs/images/logos/nextjs.svg +4 -0
- package/docs/images/logos/nuxt.svg +4 -0
- package/docs/images/logos/react.svg +5 -0
- package/docs/images/logos/svelte.svg +4 -0
- package/docs/images/logos/vue.svg +5 -0
- package/docs/images/mcp-install.png +0 -0
- package/docs/images/onboarding-mcp.png +0 -0
- package/docs/insforge-instructions-sdk.md +89 -407
- package/docs/introduction.mdx +45 -0
- package/docs/logo/dark.svg +22 -0
- package/docs/logo/light.svg +20 -0
- package/docs/partnership.mdx +652 -0
- package/docs/quickstart.mdx +83 -0
- package/docs/showcase/2048-arena.png +0 -0
- package/docs/showcase/framegen-cloud.png +0 -0
- package/docs/showcase/line-connect-race.png +0 -0
- package/docs/showcase/moment-vibe.png +0 -0
- package/docs/showcase/national-flags.png +0 -0
- package/docs/showcase/pokemon-vibe.png +0 -0
- package/docs/showcase/pure-browse-buy.png +0 -0
- package/docs/showcase.mdx +52 -0
- package/docs/snippets/sdk-installation.mdx +22 -0
- package/docs/snippets/service-icons.mdx +27 -0
- package/eslint.config.js +10 -3
- 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 -63
- package/frontend/src/App.tsx +13 -82
- 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 -0
- package/frontend/src/assets/icons/error.svg +3 -3
- package/frontend/src/assets/icons/loader.svg +9 -0
- 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/apple.svg +4 -0
- package/frontend/src/assets/logos/claude_code.svg +3 -3
- package/frontend/src/assets/logos/cline.svg +6 -6
- package/frontend/src/assets/logos/cursor.svg +20 -20
- package/frontend/src/assets/logos/discord.svg +8 -8
- package/frontend/src/assets/logos/facebook.svg +3 -0
- 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 +2 -0
- package/frontend/src/assets/logos/linkedin.svg +3 -0
- package/frontend/src/assets/logos/microsoft.svg +1 -0
- package/frontend/src/assets/logos/openai.svg +10 -10
- package/frontend/src/assets/logos/roo_code.svg +9 -9
- package/frontend/src/assets/logos/spotify.svg +17 -0
- package/frontend/src/assets/logos/tiktok.svg +6 -0
- 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 -0
- package/frontend/src/components/Checkbox.tsx +27 -29
- package/frontend/src/components/CodeBlock.tsx +55 -2
- package/frontend/src/components/CodeEditor.tsx +92 -0
- package/frontend/src/components/ConfirmDialog.tsx +1 -1
- package/frontend/src/components/ConnectCTA.tsx +38 -0
- package/frontend/src/components/CopyButton.tsx +52 -15
- package/frontend/src/components/ErrorState.tsx +1 -2
- package/frontend/src/components/FeatureSidebar.tsx +6 -6
- package/frontend/src/components/FeatureSidebarItem.tsx +2 -2
- package/frontend/src/components/JsonHighlight.tsx +21 -9
- package/frontend/src/components/ProjectInfoModal.tsx +128 -0
- package/frontend/src/components/PromptDialog.tsx +1 -4
- package/frontend/src/components/SearchInput.tsx +1 -2
- package/frontend/src/components/Stepper.tsx +53 -0
- package/frontend/src/components/ThemeToggle.tsx +3 -3
- package/frontend/src/components/datagrid/DataGrid.tsx +25 -32
- package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +1 -2
- package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +2 -4
- package/frontend/src/components/datagrid/index.ts +23 -0
- package/frontend/src/components/index.ts +23 -30
- package/frontend/src/components/layout/AppHeader.tsx +131 -91
- package/frontend/src/components/layout/AppSidebar.tsx +80 -170
- package/frontend/src/components/layout/Layout.tsx +12 -23
- package/frontend/src/components/layout/PrimaryMenu.tsx +187 -0
- package/frontend/src/components/layout/SecondaryMenu.tsx +70 -0
- package/frontend/src/components/layout/index.ts +5 -0
- package/frontend/src/components/radix/Tooltip.tsx +24 -13
- package/frontend/src/components/radix/index.ts +22 -0
- package/frontend/src/features/ai/components/AIConfigCard.tsx +129 -83
- package/frontend/src/features/ai/components/AIEmptyState.tsx +12 -7
- package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +101 -0
- package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -0
- package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -0
- package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -0
- package/frontend/src/features/ai/components/index.ts +6 -0
- package/frontend/src/features/ai/helpers.ts +57 -71
- package/frontend/src/features/ai/hooks/useAIConfigs.ts +39 -113
- package/frontend/src/features/ai/hooks/useAIUsage.ts +0 -2
- package/frontend/src/features/ai/pages/AIPage.tsx +166 -0
- package/frontend/src/features/ai/services/ai.service.ts +5 -5
- package/frontend/src/features/auth/components/AuthPreview.tsx +96 -0
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +54 -30
- package/frontend/src/features/auth/components/UserFormDialog.tsx +13 -6
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +50 -14
- package/frontend/src/features/auth/components/index.ts +5 -0
- package/frontend/src/features/auth/helpers.tsx +208 -0
- package/frontend/src/features/auth/hooks/useAnonToken.ts +30 -0
- package/frontend/src/features/auth/hooks/useAuthConfig.ts +48 -0
- package/frontend/src/features/auth/hooks/useOAuthConfig.ts +14 -10
- package/frontend/src/features/auth/hooks/useUsers.ts +43 -5
- package/frontend/src/features/auth/index.ts +3 -2
- package/frontend/src/features/auth/pages/AuthMethodsPage.tsx +275 -0
- package/frontend/src/features/auth/pages/ConfigurationPage.tsx +395 -0
- package/frontend/src/features/auth/pages/UsersPage.tsx +257 -0
- package/frontend/src/features/auth/services/anonToken.service.ts +11 -0
- package/frontend/src/features/auth/services/config.service.ts +19 -0
- package/frontend/src/features/auth/services/{oauth.service.ts → oauth-config.service.ts} +4 -4
- package/frontend/src/features/auth/services/{auth.service.ts → user.service.ts} +7 -53
- package/frontend/src/features/dashboard/components/ConnectionSuccessBanner.tsx +35 -0
- package/frontend/src/features/dashboard/components/PromptCard.tsx +21 -0
- package/frontend/src/features/dashboard/components/PromptDialog.tsx +103 -0
- package/frontend/src/features/dashboard/components/StatsCard.tsx +50 -0
- package/frontend/src/features/dashboard/components/index.ts +4 -0
- package/frontend/src/features/dashboard/pages/DashboardPage.tsx +212 -0
- package/frontend/src/features/dashboard/prompts/ai-chatbot.ts +13 -0
- package/frontend/src/features/dashboard/prompts/crm-system.ts +13 -0
- package/frontend/src/features/dashboard/prompts/ecommerce-platform.ts +12 -0
- package/frontend/src/features/dashboard/prompts/index.ts +31 -0
- package/frontend/src/features/dashboard/prompts/instagram-clone.ts +11 -0
- package/frontend/src/features/dashboard/prompts/notion-clone.ts +14 -0
- package/frontend/src/features/dashboard/prompts/reddit-clone.ts +12 -0
- package/frontend/src/features/database/components/DatabaseDataGrid.tsx +48 -17
- package/frontend/src/features/database/components/ForeignKeyCell.tsx +15 -34
- package/frontend/src/features/database/components/ForeignKeyPopover.tsx +19 -20
- package/frontend/src/features/database/components/LinkRecordModal.tsx +120 -125
- package/frontend/src/features/database/components/RecordFormDialog.tsx +22 -33
- package/frontend/src/features/database/components/RecordFormField.tsx +45 -47
- package/frontend/src/features/database/components/SQLModal.tsx +75 -0
- package/frontend/src/features/database/components/TableEmptyState.tsx +6 -5
- package/frontend/src/features/database/components/TableForm.tsx +28 -19
- package/frontend/src/features/database/components/TableFormColumn.tsx +2 -3
- package/frontend/src/features/database/components/TableSidebar.tsx +1 -1
- package/frontend/src/features/database/components/TablesEmptyState.tsx +48 -0
- package/frontend/src/features/database/components/TemplateCard.tsx +37 -0
- package/frontend/src/features/database/components/TemplatePreview.tsx +92 -0
- package/frontend/src/features/database/components/index.ts +19 -0
- package/frontend/src/features/database/constants.ts +28 -2
- package/frontend/src/features/database/contexts/SQLEditorContext.tsx +188 -0
- package/frontend/src/features/database/helpers.ts +2 -2
- package/frontend/src/features/database/hooks/useCSVImport.ts +29 -0
- package/frontend/src/features/database/hooks/useDatabase.ts +66 -0
- package/frontend/src/features/database/hooks/useRawSQL.ts +55 -0
- package/frontend/src/features/database/hooks/useRecords.ts +139 -0
- package/frontend/src/features/database/hooks/useTables.ts +135 -0
- package/frontend/src/features/database/index.ts +7 -1
- package/frontend/src/features/database/pages/FunctionsPage.tsx +203 -0
- package/frontend/src/features/database/pages/IndexesPage.tsx +228 -0
- package/frontend/src/features/database/pages/PoliciesPage.tsx +237 -0
- package/frontend/src/features/database/pages/SQLEditorPage.tsx +382 -0
- package/frontend/src/features/database/{page/DatabasePage.tsx → pages/TablesPage.tsx} +168 -209
- package/frontend/src/features/database/pages/TemplatesPage.tsx +39 -0
- package/frontend/src/features/database/pages/TriggersPage.tsx +230 -0
- package/frontend/src/features/database/services/advance.service.ts +40 -0
- package/frontend/src/features/database/services/database.service.ts +33 -194
- package/frontend/src/features/database/services/record.service.ts +219 -0
- package/frontend/src/features/database/services/table.service.ts +58 -0
- package/frontend/src/features/database/templates/ai-chatbot.ts +402 -0
- package/frontend/src/features/database/templates/crm-system.ts +528 -0
- package/frontend/src/features/database/templates/ecommerce-platform.ts +553 -0
- package/frontend/src/features/database/templates/index.ts +34 -0
- package/frontend/src/features/database/templates/instagram-clone.ts +222 -0
- package/frontend/src/features/database/templates/notion-clone.ts +483 -0
- package/frontend/src/features/database/templates/reddit-clone.ts +526 -0
- package/frontend/src/features/functions/components/FunctionRow.tsx +2 -1
- package/frontend/src/features/functions/components/FunctionsSidebar.tsx +1 -1
- package/frontend/src/features/functions/components/SecretRow.tsx +1 -1
- package/frontend/src/features/functions/components/index.ts +5 -0
- package/frontend/src/features/functions/hooks/useFunctions.ts +4 -4
- package/frontend/src/features/{secrets → functions}/hooks/useSecrets.ts +5 -5
- package/frontend/src/features/functions/pages/FunctionsPage.tsx +148 -0
- package/frontend/src/features/functions/{components/SecretsContent.tsx → pages/SecretsPage.tsx} +19 -21
- package/frontend/src/features/functions/services/{functions.service.ts → function.service.ts} +2 -2
- package/frontend/src/features/{secrets/services/secrets.service.ts → functions/services/secret.service.ts} +2 -2
- package/frontend/src/features/login/hooks/usePartnerOrigin.ts +27 -0
- package/frontend/src/features/login/pages/CloudLoginPage.tsx +118 -0
- package/frontend/src/features/login/{page → pages}/LoginPage.tsx +16 -23
- package/frontend/src/features/login/services/partnership.service.ts +65 -0
- package/frontend/src/features/logs/components/LogsDataGrid.tsx +89 -0
- package/frontend/src/features/logs/components/SeverityBadge.tsx +18 -0
- package/frontend/src/features/logs/components/index.ts +2 -0
- package/frontend/src/features/logs/helpers.ts +24 -0
- package/frontend/src/features/logs/hooks/useAuditLogs.ts +4 -4
- package/frontend/src/features/logs/hooks/useLogSources.ts +137 -0
- package/frontend/src/features/logs/hooks/useLogs.ts +163 -0
- package/frontend/src/features/logs/hooks/useMcpUsage.ts +128 -0
- package/frontend/src/features/logs/index.ts +8 -2
- package/frontend/src/features/logs/{page → pages}/AuditsPage.tsx +91 -38
- package/frontend/src/features/logs/pages/LogsPage.tsx +152 -0
- package/frontend/src/features/logs/pages/MCPLogsPage.tsx +84 -0
- package/frontend/src/features/logs/services/audit.service.ts +63 -0
- package/frontend/src/features/logs/services/log.service.ts +15 -110
- package/frontend/src/features/logs/services/usage.service.ts +31 -0
- package/frontend/src/features/onboard/components/McpConnectionStatus.tsx +68 -0
- package/frontend/src/features/onboard/components/OnboardingModal.tsx +267 -0
- package/frontend/src/features/onboard/components/VideoDemoModal.tsx +38 -0
- package/frontend/src/features/onboard/components/index.ts +4 -0
- package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +2 -2
- package/frontend/src/features/onboard/components/mcp/{mcp-helper.tsx → helpers.tsx} +8 -8
- package/frontend/src/features/onboard/components/mcp/index.ts +2 -3
- package/frontend/src/features/onboard/index.ts +13 -3
- 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/storage/components/BucketEmptyState.tsx +9 -6
- package/frontend/src/features/storage/components/BucketFormDialog.tsx +25 -41
- package/frontend/src/features/storage/components/FilePreviewDialog.tsx +20 -8
- package/frontend/src/features/storage/components/StorageDataGrid.tsx +4 -3
- package/frontend/src/features/storage/components/StorageManager.tsx +23 -34
- package/frontend/src/features/storage/components/index.ts +12 -0
- package/frontend/src/features/storage/hooks/useStorage.ts +208 -0
- package/frontend/src/features/storage/{page → pages}/StoragePage.tsx +41 -143
- package/frontend/src/features/storage/services/storage.service.ts +22 -1
- package/frontend/src/features/visualizer/components/AuthNode.tsx +72 -56
- package/frontend/src/features/visualizer/components/BucketNode.tsx +4 -4
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +108 -80
- package/frontend/src/features/visualizer/components/TableNode.tsx +34 -41
- package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +12 -4
- package/frontend/src/features/visualizer/pages/VisualizerPage.tsx +97 -0
- package/frontend/src/index.css +1 -0
- package/frontend/src/lib/analytics/posthog.tsx +27 -0
- package/frontend/src/lib/contexts/AuthContext.tsx +38 -31
- package/frontend/src/lib/contexts/SocketContext.tsx +123 -80
- package/frontend/src/{features/metadata → lib}/hooks/useMetadata.ts +1 -1
- package/frontend/src/lib/hooks/useToast.tsx +6 -2
- package/frontend/src/lib/routing/AppRoutes.tsx +99 -0
- package/frontend/src/lib/routing/RequireAuth.tsx +27 -0
- package/frontend/src/lib/utils/cloudMessaging.ts +20 -0
- package/frontend/src/lib/utils/menuItems.ts +207 -0
- package/frontend/src/lib/utils/{validation-schemas.ts → schemaValidations.ts} +10 -5
- package/frontend/src/lib/utils/utils.ts +32 -1
- package/frontend/src/vite-env.d.ts +1 -0
- package/frontend/tsconfig.json +25 -25
- package/frontend/tsconfig.node.json +9 -9
- package/frontend/vite.config.ts +5 -3
- package/functions/deno.json +24 -24
- package/functions/server.ts +315 -290
- package/functions/worker-template.js +15 -4
- package/i18n/README.ar.md +130 -0
- package/i18n/README.de.md +130 -0
- package/i18n/README.es.md +154 -0
- package/i18n/README.fr.md +134 -0
- package/i18n/README.hi.md +129 -0
- package/i18n/README.ja.md +174 -0
- package/i18n/README.ko.md +137 -0
- package/i18n/README.pt-BR.md +131 -0
- package/i18n/README.ru.md +129 -0
- package/i18n/README.zh-CN.md +133 -0
- package/openapi/ai.yaml +715 -688
- package/openapi/auth.yaml +1244 -563
- package/openapi/email.yaml +158 -0
- package/openapi/functions.yaml +475 -475
- package/openapi/health.yaml +29 -29
- package/openapi/logs.yaml +223 -223
- package/openapi/metadata.yaml +177 -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 +463 -463
- package/package.json +97 -88
- package/shared-schemas/package.json +31 -31
- package/shared-schemas/src/ai-api.schema.ts +34 -58
- package/shared-schemas/src/ai.schema.ts +63 -54
- package/shared-schemas/src/auth-api.schema.ts +352 -193
- package/shared-schemas/src/auth.schema.ts +43 -7
- package/shared-schemas/src/cloud-events.schema.ts +57 -0
- package/shared-schemas/src/database-api.schema.ts +35 -4
- package/shared-schemas/src/database.schema.ts +40 -1
- package/shared-schemas/src/docs.schema.ts +26 -0
- package/shared-schemas/src/email-api.schema.ts +30 -0
- package/shared-schemas/src/index.ts +5 -0
- package/shared-schemas/src/logs-api.schema.ts +7 -1
- package/shared-schemas/src/logs.schema.ts +26 -0
- package/shared-schemas/src/metadata.schema.ts +18 -4
- package/shared-schemas/src/realtime-api.schema.ts +111 -0
- package/shared-schemas/src/realtime.schema.ts +143 -0
- package/shared-schemas/tsconfig.json +21 -21
- package/tsconfig.json +7 -7
- package/zeabur/README.md +13 -0
- package/zeabur/template.yml +1032 -0
- package/.github/workflows/deploy-aws.yml +0 -130
- package/backend/src/api/routes/agent.ts +0 -29
- package/backend/src/api/routes/auth.oauth.ts +0 -482
- package/backend/src/api/routes/auth.ts +0 -386
- package/backend/src/api/routes/docs.ts +0 -66
- package/backend/src/api/routes/functions.ts +0 -183
- package/backend/src/api/routes/openapi.ts +0 -82
- package/backend/src/api/routes/usage.ts +0 -96
- package/backend/src/core/ai/client.ts +0 -242
- package/backend/src/core/ai/model.ts +0 -117
- package/backend/src/core/auth/auth.ts +0 -780
- package/backend/src/core/database/manager.ts +0 -178
- package/backend/src/core/database/table.ts +0 -772
- package/backend/src/core/documentation/agent.ts +0 -689
- package/backend/src/core/documentation/openapi.ts +0 -856
- package/backend/src/core/logs/analytics.ts +0 -76
- package/backend/src/core/logs/providers/localdb.provider.ts +0 -246
- package/backend/src/core/socket/socket.ts +0 -388
- package/backend/src/core/storage/storage.ts +0 -923
- package/backend/src/utils/cloud-token.ts +0 -39
- package/backend/src/utils/helpers.ts +0 -49
- package/backend/src/utils/uuid.ts +0 -9
- package/backend/tests/manual/test-better-auth.sh +0 -303
- package/docker-init/db/logs.sql +0 -9
- package/frontend/README.md +0 -112
- package/frontend/src/components/datagrid/index.tsx +0 -20
- package/frontend/src/components/layout/CloudLayout.tsx +0 -95
- package/frontend/src/features/ai/components/AIConfigDialog.tsx +0 -76
- package/frontend/src/features/ai/components/AIConfigForm.tsx +0 -222
- package/frontend/src/features/ai/components/fields/ModalityField.tsx +0 -87
- package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +0 -134
- package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +0 -33
- package/frontend/src/features/ai/page/AIPage.tsx +0 -178
- package/frontend/src/features/auth/components/AddOAuthDialog.tsx +0 -106
- package/frontend/src/features/auth/components/AuthMethodTab.tsx +0 -238
- package/frontend/src/features/auth/components/UsersTab.tsx +0 -114
- package/frontend/src/features/auth/page/AuthenticationPage.tsx +0 -169
- package/frontend/src/features/dashboard/page/DashboardPage.tsx +0 -194
- package/frontend/src/features/database/hooks/UseLinkModal.tsx +0 -78
- package/frontend/src/features/functions/components/FunctionViewer.tsx +0 -46
- package/frontend/src/features/functions/components/FunctionsContent.tsx +0 -88
- package/frontend/src/features/functions/page/FunctionsPage.tsx +0 -28
- package/frontend/src/features/login/components/AuthErrorBoundary.tsx +0 -87
- package/frontend/src/features/login/components/PrivateRoute.tsx +0 -24
- package/frontend/src/features/login/page/CloudLoginPage.tsx +0 -93
- package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +0 -313
- package/frontend/src/features/logs/components/LogsTable.tsx +0 -199
- package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +0 -530
- package/frontend/src/features/metadata/index.ts +0 -0
- package/frontend/src/features/metadata/page/MetadataPage.tsx +0 -136
- package/frontend/src/features/onboard/components/CompletionCard.tsx +0 -41
- package/frontend/src/features/onboard/components/OnboardButton.tsx +0 -84
- package/frontend/src/features/onboard/components/StepContent.tsx +0 -91
- package/frontend/src/features/onboard/components/TestConnectionStep.tsx +0 -53
- package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +0 -144
- package/frontend/src/features/onboard/page/OnBoardPage.tsx +0 -104
- package/frontend/src/features/onboard/types.ts +0 -8
- package/frontend/src/features/visualizer/page/VisualizerPage.tsx +0 -127
- package/frontend/src/lib/contexts/OnboardStepContext.tsx +0 -68
- package/frontend/src/lib/hooks/useOnboardingCompletion.ts +0 -29
- /package/backend/src/api/{middleware → middlewares}/error.ts +0 -0
- /package/backend/src/api/{middleware → middlewares}/upload.ts +0 -0
- /package/frontend/src/{features/metadata → lib}/services/metadata.service.ts +0 -0
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
import { DatabaseManager } from '@/infra/database/database.manager.js';
|
|
2
3
|
import logger from '@/utils/logger.js';
|
|
3
|
-
import { AppError } from '@/api/
|
|
4
|
+
import { AppError } from '@/api/middlewares/error.js';
|
|
4
5
|
import { ERROR_CODES } from '@/types/error-constants.js';
|
|
5
6
|
import type { AuditLogEntry, AuditLogQuery } from '@/types/logs.js';
|
|
6
7
|
import { AuditLogSchema, GetAuditLogStatsResponse } from '@insforge/shared-schemas';
|
|
7
8
|
|
|
8
9
|
export class AuditService {
|
|
9
10
|
private static instance: AuditService;
|
|
10
|
-
private
|
|
11
|
+
private pool: Pool | null = null;
|
|
11
12
|
|
|
12
13
|
private constructor() {
|
|
13
|
-
const dbManager = DatabaseManager.getInstance();
|
|
14
|
-
this.db = dbManager.getDb();
|
|
15
14
|
logger.info('AuditService initialized');
|
|
16
15
|
}
|
|
17
16
|
|
|
17
|
+
private getPool(): Pool {
|
|
18
|
+
if (!this.pool) {
|
|
19
|
+
this.pool = DatabaseManager.getInstance().getPool();
|
|
20
|
+
}
|
|
21
|
+
return this.pool;
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
public static getInstance(): AuditService {
|
|
19
25
|
if (!AuditService.instance) {
|
|
20
26
|
AuditService.instance = new AuditService();
|
|
@@ -27,19 +33,21 @@ export class AuditService {
|
|
|
27
33
|
*/
|
|
28
34
|
async log(entry: AuditLogEntry): Promise<AuditLogSchema> {
|
|
29
35
|
try {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.get(
|
|
36
|
+
const pool = this.getPool();
|
|
37
|
+
const result = await pool.query(
|
|
38
|
+
`INSERT INTO _audit_logs (actor, action, module, details, ip_address)
|
|
39
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
40
|
+
RETURNING *`,
|
|
41
|
+
[
|
|
37
42
|
entry.actor,
|
|
38
43
|
entry.action,
|
|
39
44
|
entry.module,
|
|
40
45
|
entry.details ? JSON.stringify(entry.details) : null,
|
|
41
|
-
entry.ip_address || null
|
|
42
|
-
|
|
46
|
+
entry.ip_address || null,
|
|
47
|
+
]
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const row = result.rows[0];
|
|
43
51
|
|
|
44
52
|
logger.info('Audit log created', {
|
|
45
53
|
actor: entry.actor,
|
|
@@ -48,14 +56,14 @@ export class AuditService {
|
|
|
48
56
|
});
|
|
49
57
|
|
|
50
58
|
return {
|
|
51
|
-
id:
|
|
52
|
-
actor:
|
|
53
|
-
action:
|
|
54
|
-
module:
|
|
55
|
-
details:
|
|
56
|
-
ipAddress:
|
|
57
|
-
createdAt:
|
|
58
|
-
updatedAt:
|
|
59
|
+
id: row.id,
|
|
60
|
+
actor: row.actor,
|
|
61
|
+
action: row.action,
|
|
62
|
+
module: row.module,
|
|
63
|
+
details: row.details,
|
|
64
|
+
ipAddress: row.ip_address,
|
|
65
|
+
createdAt: row.created_at,
|
|
66
|
+
updatedAt: row.updated_at,
|
|
59
67
|
};
|
|
60
68
|
} catch (error) {
|
|
61
69
|
logger.error('Failed to create audit log', error);
|
|
@@ -68,6 +76,8 @@ export class AuditService {
|
|
|
68
76
|
*/
|
|
69
77
|
async query(query: AuditLogQuery): Promise<{ records: AuditLogSchema[]; total: number }> {
|
|
70
78
|
try {
|
|
79
|
+
const pool = this.getPool();
|
|
80
|
+
|
|
71
81
|
// Build base WHERE clause
|
|
72
82
|
let whereClause = 'WHERE 1=1';
|
|
73
83
|
const params: unknown[] = [];
|
|
@@ -100,8 +110,8 @@ export class AuditService {
|
|
|
100
110
|
|
|
101
111
|
// Get total count first
|
|
102
112
|
const countSql = `SELECT COUNT(*) as count FROM _audit_logs ${whereClause}`;
|
|
103
|
-
const countResult =
|
|
104
|
-
const total = countResult.count;
|
|
113
|
+
const countResult = await pool.query(countSql, params);
|
|
114
|
+
const total = parseInt(countResult.rows[0].count, 10);
|
|
105
115
|
|
|
106
116
|
// Get paginated records
|
|
107
117
|
let dataSql = `SELECT * FROM _audit_logs ${whereClause} ORDER BY created_at DESC`;
|
|
@@ -117,10 +127,10 @@ export class AuditService {
|
|
|
117
127
|
dataParams.push(query.offset);
|
|
118
128
|
}
|
|
119
129
|
|
|
120
|
-
const
|
|
130
|
+
const dataResult = await pool.query(dataSql, dataParams);
|
|
121
131
|
|
|
122
132
|
return {
|
|
123
|
-
records:
|
|
133
|
+
records: dataResult.rows.map((record) => ({
|
|
124
134
|
id: record.id,
|
|
125
135
|
actor: record.actor,
|
|
126
136
|
action: record.action,
|
|
@@ -143,18 +153,21 @@ export class AuditService {
|
|
|
143
153
|
*/
|
|
144
154
|
async getById(id: string): Promise<AuditLogSchema | null> {
|
|
145
155
|
try {
|
|
146
|
-
const
|
|
156
|
+
const pool = this.getPool();
|
|
157
|
+
const result = await pool.query('SELECT * FROM _audit_logs WHERE id = $1', [id]);
|
|
158
|
+
|
|
159
|
+
const row = result.rows[0];
|
|
147
160
|
|
|
148
|
-
return
|
|
161
|
+
return row
|
|
149
162
|
? {
|
|
150
|
-
id:
|
|
151
|
-
actor:
|
|
152
|
-
action:
|
|
153
|
-
module:
|
|
154
|
-
details:
|
|
155
|
-
ipAddress:
|
|
156
|
-
createdAt:
|
|
157
|
-
updatedAt:
|
|
163
|
+
id: row.id,
|
|
164
|
+
actor: row.actor,
|
|
165
|
+
action: row.action,
|
|
166
|
+
module: row.module,
|
|
167
|
+
details: row.details,
|
|
168
|
+
ipAddress: row.ip_address,
|
|
169
|
+
createdAt: row.created_at,
|
|
170
|
+
updatedAt: row.updated_at,
|
|
158
171
|
}
|
|
159
172
|
: null;
|
|
160
173
|
} catch (error) {
|
|
@@ -168,50 +181,52 @@ export class AuditService {
|
|
|
168
181
|
*/
|
|
169
182
|
async getStats(days: number = 7): Promise<GetAuditLogStatsResponse> {
|
|
170
183
|
try {
|
|
184
|
+
const pool = this.getPool();
|
|
171
185
|
const startDate = new Date();
|
|
172
186
|
startDate.setDate(startDate.getDate() - days);
|
|
173
187
|
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
188
|
+
const totalLogsResult = await pool.query(
|
|
189
|
+
'SELECT COUNT(*) as count FROM _audit_logs WHERE created_at >= $1',
|
|
190
|
+
[startDate.toISOString()]
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const uniqueActorsResult = await pool.query(
|
|
194
|
+
'SELECT COUNT(DISTINCT actor) as count FROM _audit_logs WHERE created_at >= $1',
|
|
195
|
+
[startDate.toISOString()]
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const uniqueModulesResult = await pool.query(
|
|
199
|
+
'SELECT COUNT(DISTINCT module) as count FROM _audit_logs WHERE created_at >= $1',
|
|
200
|
+
[startDate.toISOString()]
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const actionsByModuleResult = await pool.query(
|
|
204
|
+
`SELECT module, COUNT(*) as count
|
|
205
|
+
FROM _audit_logs
|
|
206
|
+
WHERE created_at >= $1
|
|
207
|
+
GROUP BY module`,
|
|
208
|
+
[startDate.toISOString()]
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const recentActivityResult = await pool.query(
|
|
212
|
+
`SELECT * FROM _audit_logs
|
|
213
|
+
WHERE created_at >= $1
|
|
214
|
+
ORDER BY created_at DESC
|
|
215
|
+
LIMIT 10`,
|
|
216
|
+
[startDate.toISOString()]
|
|
217
|
+
);
|
|
203
218
|
|
|
204
219
|
const moduleStats: Record<string, number> = {};
|
|
205
|
-
|
|
206
|
-
moduleStats[row.module] = parseInt(row.count);
|
|
220
|
+
actionsByModuleResult.rows.forEach((row: { module: string; count: string }) => {
|
|
221
|
+
moduleStats[row.module] = parseInt(row.count, 10);
|
|
207
222
|
});
|
|
208
223
|
|
|
209
224
|
return {
|
|
210
|
-
totalLogs: parseInt(
|
|
211
|
-
uniqueActors: parseInt(
|
|
212
|
-
uniqueModules: parseInt(
|
|
225
|
+
totalLogs: parseInt(totalLogsResult.rows[0]?.count || '0', 10),
|
|
226
|
+
uniqueActors: parseInt(uniqueActorsResult.rows[0]?.count || '0', 10),
|
|
227
|
+
uniqueModules: parseInt(uniqueModulesResult.rows[0]?.count || '0', 10),
|
|
213
228
|
actionsByModule: moduleStats,
|
|
214
|
-
recentActivity:
|
|
229
|
+
recentActivity: recentActivityResult.rows.map((record) => ({
|
|
215
230
|
id: record.id,
|
|
216
231
|
actor: record.actor,
|
|
217
232
|
action: record.action,
|
|
@@ -233,14 +248,16 @@ export class AuditService {
|
|
|
233
248
|
*/
|
|
234
249
|
async cleanup(daysToKeep: number = 90): Promise<number> {
|
|
235
250
|
try {
|
|
251
|
+
const pool = this.getPool();
|
|
236
252
|
const cutoffDate = new Date();
|
|
237
253
|
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
|
|
238
254
|
|
|
239
|
-
const result = await
|
|
240
|
-
|
|
241
|
-
|
|
255
|
+
const result = await pool.query(
|
|
256
|
+
'DELETE FROM _audit_logs WHERE created_at < $1 RETURNING id',
|
|
257
|
+
[cutoffDate.toISOString()]
|
|
258
|
+
);
|
|
242
259
|
|
|
243
|
-
const deletedCount = result.length;
|
|
260
|
+
const deletedCount = result.rows.length;
|
|
244
261
|
|
|
245
262
|
if (deletedCount > 0) {
|
|
246
263
|
logger.info(`Cleaned up ${deletedCount} audit logs older than ${daysToKeep} days`);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import logger from '@/utils/logger.js';
|
|
2
|
+
import { CloudWatchProvider } from '@/providers/logs/cloudwatch.provider.js';
|
|
3
|
+
import { LocalFileProvider } from '@/providers/logs/local.provider.js';
|
|
4
|
+
import { LogProvider } from '@/providers/logs/base.provider.js';
|
|
5
|
+
import { LogSchema, LogSourceSchema, LogStatsSchema } from '@insforge/shared-schemas';
|
|
6
|
+
import { isCloudEnvironment } from '@/utils/environment.js';
|
|
7
|
+
|
|
8
|
+
export class LogService {
|
|
9
|
+
private static instance: LogService;
|
|
10
|
+
private provider!: LogProvider;
|
|
11
|
+
|
|
12
|
+
private constructor() {}
|
|
13
|
+
|
|
14
|
+
static getInstance(): LogService {
|
|
15
|
+
if (!LogService.instance) {
|
|
16
|
+
LogService.instance = new LogService();
|
|
17
|
+
}
|
|
18
|
+
return LogService.instance;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async initialize(): Promise<void> {
|
|
22
|
+
// Use CloudWatch if AWS credentials are available or if it's cloud environment since we provided the permissions in instance profile
|
|
23
|
+
// otherwise use file-based logging
|
|
24
|
+
const hasAwsCredentials =
|
|
25
|
+
(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) || isCloudEnvironment();
|
|
26
|
+
|
|
27
|
+
if (hasAwsCredentials) {
|
|
28
|
+
logger.info('Using log provider: CloudWatch');
|
|
29
|
+
this.provider = new CloudWatchProvider();
|
|
30
|
+
} else {
|
|
31
|
+
logger.info('Using log provider: File-based (no AWS credentials required)');
|
|
32
|
+
this.provider = new LocalFileProvider();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await this.provider.initialize();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getLogSources(): Promise<LogSourceSchema[]> {
|
|
39
|
+
return this.provider.getLogSources();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getLogsBySource(
|
|
43
|
+
sourceName: string,
|
|
44
|
+
limit: number = 100,
|
|
45
|
+
beforeTimestamp?: string
|
|
46
|
+
): Promise<{
|
|
47
|
+
logs: LogSchema[];
|
|
48
|
+
total: number;
|
|
49
|
+
tableName: string;
|
|
50
|
+
}> {
|
|
51
|
+
return this.provider.getLogsBySource(sourceName, limit, beforeTimestamp);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getLogSourceStats(): Promise<LogStatsSchema[]> {
|
|
55
|
+
return this.provider.getLogSourceStats();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
searchLogs(
|
|
59
|
+
query: string,
|
|
60
|
+
sourceName?: string,
|
|
61
|
+
limit: number = 100,
|
|
62
|
+
offset: number = 0
|
|
63
|
+
): Promise<{
|
|
64
|
+
logs: (LogSchema & { source: string })[];
|
|
65
|
+
total: number;
|
|
66
|
+
}> {
|
|
67
|
+
return this.provider.searchLogs(query, sourceName, limit, offset);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async close(): Promise<void> {
|
|
71
|
+
await this.provider.close();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -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
|
+
}
|