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
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import jwt from 'jsonwebtoken';
|
|
3
|
+
import { isCloudEnvironment } from '@/utils/environment.js';
|
|
4
|
+
import { AppError } from '@/api/middlewares/error.js';
|
|
5
|
+
import { ERROR_CODES } from '@/types/error-constants.js';
|
|
6
|
+
import logger from '@/utils/logger.js';
|
|
7
|
+
|
|
8
|
+
interface CloudCredentialsResponse {
|
|
9
|
+
openrouter?: {
|
|
10
|
+
api_key: string;
|
|
11
|
+
limit?: number;
|
|
12
|
+
expired_at?: string | null;
|
|
13
|
+
usage?: number;
|
|
14
|
+
limit_remaining?: number;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface CloudCredentials {
|
|
19
|
+
apiKey: string;
|
|
20
|
+
limitRemaining?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface OpenRouterKeyInfo {
|
|
24
|
+
data: {
|
|
25
|
+
label: string;
|
|
26
|
+
usage: number;
|
|
27
|
+
limit: number | null;
|
|
28
|
+
is_free_tier: boolean;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface OpenRouterLimitation {
|
|
33
|
+
label: string;
|
|
34
|
+
credit_limit: number | null;
|
|
35
|
+
credit_used: number;
|
|
36
|
+
credit_remaining: number | null;
|
|
37
|
+
rate_limit?: {
|
|
38
|
+
requests?: number;
|
|
39
|
+
interval?: string;
|
|
40
|
+
note?: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class OpenRouterProvider {
|
|
45
|
+
private static instance: OpenRouterProvider;
|
|
46
|
+
private cloudCredentials: CloudCredentials | undefined;
|
|
47
|
+
private openRouterClient: OpenAI | null = null;
|
|
48
|
+
private currentApiKey: string | undefined;
|
|
49
|
+
private renewalPromise: Promise<string> | null = null;
|
|
50
|
+
private fetchPromise: Promise<string> | null = null;
|
|
51
|
+
|
|
52
|
+
private constructor() {}
|
|
53
|
+
|
|
54
|
+
static getInstance(): OpenRouterProvider {
|
|
55
|
+
if (!OpenRouterProvider.instance) {
|
|
56
|
+
OpenRouterProvider.instance = new OpenRouterProvider();
|
|
57
|
+
}
|
|
58
|
+
return OpenRouterProvider.instance;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create or recreate the OpenAI client with the given API key
|
|
63
|
+
*/
|
|
64
|
+
private createClient(apiKey: string): OpenAI {
|
|
65
|
+
this.currentApiKey = apiKey;
|
|
66
|
+
return new OpenAI({
|
|
67
|
+
baseURL: 'https://openrouter.ai/api/v1',
|
|
68
|
+
apiKey,
|
|
69
|
+
defaultHeaders: {
|
|
70
|
+
'HTTP-Referer': 'https://insforge.dev',
|
|
71
|
+
'X-Title': 'InsForge',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get OpenRouter API key based on environment
|
|
78
|
+
* In cloud environment: fetches from cloud API with JWT authentication
|
|
79
|
+
* In local environment: returns from environment variable
|
|
80
|
+
*/
|
|
81
|
+
async getApiKey(): Promise<string> {
|
|
82
|
+
if (isCloudEnvironment()) {
|
|
83
|
+
if (this.cloudCredentials) {
|
|
84
|
+
return this.cloudCredentials.apiKey;
|
|
85
|
+
} else {
|
|
86
|
+
return await this.fetchCloudApiKey();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
91
|
+
if (!apiKey) {
|
|
92
|
+
throw new AppError(
|
|
93
|
+
'OPENROUTER_API_KEY not found in environment variables',
|
|
94
|
+
500,
|
|
95
|
+
ERROR_CODES.AI_INVALID_API_KEY
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return apiKey;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get the OpenAI client, creating or updating it as needed
|
|
103
|
+
* Used internally by sendRequest()
|
|
104
|
+
*/
|
|
105
|
+
private async getClient(): Promise<OpenAI> {
|
|
106
|
+
if (!this.openRouterClient) {
|
|
107
|
+
this.openRouterClient = this.createClient(await this.getApiKey());
|
|
108
|
+
return this.openRouterClient;
|
|
109
|
+
}
|
|
110
|
+
if (isCloudEnvironment()) {
|
|
111
|
+
const apiKey = await this.getApiKey();
|
|
112
|
+
if (this.currentApiKey !== apiKey) {
|
|
113
|
+
this.openRouterClient = this.createClient(apiKey);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return this.openRouterClient;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check if AI services are properly configured
|
|
121
|
+
*/
|
|
122
|
+
isConfigured(): boolean {
|
|
123
|
+
if (isCloudEnvironment()) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return !!process.env.OPENROUTER_API_KEY;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get remaining credits for the current API key from OpenRouter
|
|
131
|
+
*/
|
|
132
|
+
async getRemainingCredits(): Promise<{
|
|
133
|
+
usage: number;
|
|
134
|
+
limit: number | null;
|
|
135
|
+
remaining: number | null;
|
|
136
|
+
}> {
|
|
137
|
+
try {
|
|
138
|
+
const apiKey = await this.getApiKey();
|
|
139
|
+
|
|
140
|
+
if (isCloudEnvironment()) {
|
|
141
|
+
// Use InsForge API for cloud environment
|
|
142
|
+
const response = await fetch(
|
|
143
|
+
`https://api.insforge.dev/ai/v1/limitations?credential=${encodeURIComponent(apiKey)}`,
|
|
144
|
+
{
|
|
145
|
+
method: 'GET',
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
throw new Error(`Failed to fetch key info: ${response.statusText}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = (await response.json()) as { data: OpenRouterLimitation };
|
|
154
|
+
const keyInfo = result.data;
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
usage: keyInfo.credit_used,
|
|
158
|
+
limit: keyInfo.credit_limit,
|
|
159
|
+
remaining: keyInfo.credit_remaining,
|
|
160
|
+
};
|
|
161
|
+
} else {
|
|
162
|
+
// Use OpenRouter API for local environment
|
|
163
|
+
const response = await fetch('https://openrouter.ai/api/v1/key', {
|
|
164
|
+
method: 'GET',
|
|
165
|
+
headers: {
|
|
166
|
+
Authorization: `Bearer ${apiKey}`,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
throw new AppError(
|
|
172
|
+
`Invalid OpenRouter API Key`,
|
|
173
|
+
500,
|
|
174
|
+
ERROR_CODES.AI_INVALID_API_KEY,
|
|
175
|
+
'Check your OpenRouter key and try again.'
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const keyInfo = (await response.json()) as OpenRouterKeyInfo;
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
usage: keyInfo.data.usage,
|
|
183
|
+
limit: keyInfo.data.limit,
|
|
184
|
+
remaining: keyInfo.data.limit !== null ? keyInfo.data.limit - keyInfo.data.usage : null,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('Failed to fetch remaining credits:', error);
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Fetch API key from cloud service
|
|
195
|
+
* Uses promise memoization to prevent duplicate fetch requests
|
|
196
|
+
*/
|
|
197
|
+
private async fetchCloudApiKey(): Promise<string> {
|
|
198
|
+
// If fetch is already in progress, wait for it
|
|
199
|
+
if (this.fetchPromise) {
|
|
200
|
+
logger.info('Fetch already in progress, waiting for completion...');
|
|
201
|
+
return this.fetchPromise;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Start new fetch and store the promise
|
|
205
|
+
this.fetchPromise = (async () => {
|
|
206
|
+
try {
|
|
207
|
+
const projectId = process.env.PROJECT_ID;
|
|
208
|
+
if (!projectId) {
|
|
209
|
+
throw new Error('PROJECT_ID not found in environment variables');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const jwtSecret = process.env.JWT_SECRET;
|
|
213
|
+
if (!jwtSecret) {
|
|
214
|
+
throw new Error('JWT_SECRET not found in environment variables');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Sign a token for authentication
|
|
218
|
+
const token = jwt.sign({ projectId }, jwtSecret, { expiresIn: '1h' });
|
|
219
|
+
|
|
220
|
+
// Fetch API key from cloud service with sign token as query parameter
|
|
221
|
+
const response = await fetch(
|
|
222
|
+
`${process.env.CLOUD_API_HOST || 'https://api.insforge.dev'}/ai/v1/credentials/${projectId}?sign=${token}`
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
if (!response.ok) {
|
|
226
|
+
throw new Error(`Failed to fetch cloud API key: ${response.statusText}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const data = (await response.json()) as CloudCredentialsResponse;
|
|
230
|
+
|
|
231
|
+
// Extract API key from the openrouter object in response
|
|
232
|
+
if (!data.openrouter?.api_key) {
|
|
233
|
+
throw new Error('Invalid response: missing openrouter API Key');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Store credentials with metadata
|
|
237
|
+
this.cloudCredentials = {
|
|
238
|
+
apiKey: data.openrouter.api_key,
|
|
239
|
+
limitRemaining: data.openrouter.limit_remaining,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
logger.info('Successfully fetched cloud API key');
|
|
243
|
+
|
|
244
|
+
return data.openrouter.api_key;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('Failed to fetch cloud API key:', error);
|
|
247
|
+
throw error;
|
|
248
|
+
} finally {
|
|
249
|
+
// Clear the promise after completion (success or failure)
|
|
250
|
+
this.fetchPromise = null;
|
|
251
|
+
}
|
|
252
|
+
})();
|
|
253
|
+
|
|
254
|
+
return this.fetchPromise;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Renew API key from cloud service when credits are exhausted
|
|
259
|
+
* Uses promise memoization to prevent duplicate renewal requests
|
|
260
|
+
*/
|
|
261
|
+
async renewCloudApiKey(): Promise<string> {
|
|
262
|
+
// If renewal is already in progress, wait for it
|
|
263
|
+
if (this.renewalPromise) {
|
|
264
|
+
logger.info('Renewal already in progress, waiting for completion...');
|
|
265
|
+
return this.renewalPromise;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Start new renewal and store the promise
|
|
269
|
+
this.renewalPromise = (async () => {
|
|
270
|
+
try {
|
|
271
|
+
const projectId = process.env.PROJECT_ID;
|
|
272
|
+
if (!projectId) {
|
|
273
|
+
throw new Error('PROJECT_ID not found in environment variables');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const jwtSecret = process.env.JWT_SECRET;
|
|
277
|
+
if (!jwtSecret) {
|
|
278
|
+
throw new Error('JWT_SECRET not found in environment variables');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Sign a token for authentication
|
|
282
|
+
const token = jwt.sign({ projectId }, jwtSecret, { expiresIn: '1h' });
|
|
283
|
+
|
|
284
|
+
// Renew API key from cloud service with sign token in request body
|
|
285
|
+
const response = await fetch(
|
|
286
|
+
`${process.env.CLOUD_API_HOST || 'https://api.insforge.dev'}/ai/v1/credentials/${projectId}/renew`,
|
|
287
|
+
{
|
|
288
|
+
method: 'POST',
|
|
289
|
+
headers: {
|
|
290
|
+
'Content-Type': 'application/json',
|
|
291
|
+
},
|
|
292
|
+
body: JSON.stringify({ sign: token }),
|
|
293
|
+
}
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
if (!response.ok) {
|
|
297
|
+
throw new Error(`Failed to renew cloud API key: ${response.statusText}`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const data = (await response.json()) as CloudCredentialsResponse;
|
|
301
|
+
|
|
302
|
+
// Extract API key from the openrouter object in response
|
|
303
|
+
if (!data.openrouter?.api_key) {
|
|
304
|
+
throw new Error('Invalid response: missing openrouter API Key');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Store credentials with metadata
|
|
308
|
+
this.cloudCredentials = {
|
|
309
|
+
apiKey: data.openrouter.api_key,
|
|
310
|
+
limitRemaining: data.openrouter.limit_remaining,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
logger.info('Successfully renewed cloud API key');
|
|
314
|
+
|
|
315
|
+
// Wait for OpenRouter to propagate the updated credits
|
|
316
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
317
|
+
|
|
318
|
+
return data.openrouter.api_key;
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.error('Failed to renew cloud API key:', error);
|
|
321
|
+
throw error;
|
|
322
|
+
} finally {
|
|
323
|
+
// Clear the promise after completion (success or failure)
|
|
324
|
+
this.renewalPromise = null;
|
|
325
|
+
}
|
|
326
|
+
})();
|
|
327
|
+
|
|
328
|
+
return this.renewalPromise;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Send a request to OpenRouter with automatic renewal and retry logic
|
|
333
|
+
* Handles 403 insufficient credits errors by renewing the API key and retrying
|
|
334
|
+
* @param request - Function that takes an OpenAI client and returns a Promise
|
|
335
|
+
* @returns The result of the request
|
|
336
|
+
*/
|
|
337
|
+
async sendRequest<T>(request: (client: OpenAI) => Promise<T>): Promise<T> {
|
|
338
|
+
const client = await this.getClient();
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
return await request(client);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
// Check if error is a 402/403 insufficient credits error in cloud environment
|
|
344
|
+
if (
|
|
345
|
+
isCloudEnvironment() &&
|
|
346
|
+
error instanceof OpenAI.APIError &&
|
|
347
|
+
(error.status === 402 || error.status === 403)
|
|
348
|
+
) {
|
|
349
|
+
logger.info(`Received ${error.status} insufficient credits, renewing API key...`);
|
|
350
|
+
await this.renewCloudApiKey();
|
|
351
|
+
|
|
352
|
+
// Get fresh client with renewed API key
|
|
353
|
+
const renewedClient = await this.getClient();
|
|
354
|
+
|
|
355
|
+
// Retry with exponential backoff (3 attempts)
|
|
356
|
+
const maxRetries = 3;
|
|
357
|
+
|
|
358
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
359
|
+
try {
|
|
360
|
+
const backoffMs = Math.pow(2, attempt - 1) * 1000; // 1s, 2s, 4s
|
|
361
|
+
logger.info(
|
|
362
|
+
`Retrying request after renewal (attempt ${attempt}/${maxRetries}), waiting ${backoffMs}ms...`
|
|
363
|
+
);
|
|
364
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
365
|
+
|
|
366
|
+
const result = await request(renewedClient);
|
|
367
|
+
logger.info('Request succeeded after API key renewal');
|
|
368
|
+
return result;
|
|
369
|
+
} catch (retryError) {
|
|
370
|
+
if (attempt === maxRetries) {
|
|
371
|
+
logger.error(`All ${maxRetries} retry attempts failed after API key renewal`);
|
|
372
|
+
throw retryError;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
throw error;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { EmailTemplate } from '@/types/email.js';
|
|
2
|
+
import { SendRawEmailRequest } from '@insforge/shared-schemas';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Email provider interface
|
|
6
|
+
* Defines the contract that all email providers must implement
|
|
7
|
+
*/
|
|
8
|
+
export interface EmailProvider {
|
|
9
|
+
/**
|
|
10
|
+
* Initialize the email provider (optional)
|
|
11
|
+
*/
|
|
12
|
+
initialize?(): void | Promise<void>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Send email using predefined template
|
|
16
|
+
* @param email - Recipient email address
|
|
17
|
+
* @param name - Recipient name
|
|
18
|
+
* @param template - Template type
|
|
19
|
+
* @param variables - Variables to use in the email template
|
|
20
|
+
*/
|
|
21
|
+
sendWithTemplate(
|
|
22
|
+
email: string,
|
|
23
|
+
name: string,
|
|
24
|
+
template: EmailTemplate,
|
|
25
|
+
variables?: Record<string, string>
|
|
26
|
+
): Promise<void>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Send custom/raw email (optional - not all providers may support this)
|
|
30
|
+
* @param options - Email options (to, subject, html, cc, bcc, from, replyTo)
|
|
31
|
+
*/
|
|
32
|
+
sendRaw?(options: SendRawEmailRequest): Promise<void>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if provider supports template-based emails
|
|
36
|
+
*/
|
|
37
|
+
supportsTemplates(): boolean;
|
|
38
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { config } from '@/infra/config/app.config.js';
|
|
4
|
+
import logger from '@/utils/logger.js';
|
|
5
|
+
import { AppError } from '@/api/middlewares/error.js';
|
|
6
|
+
import { ERROR_CODES } from '@/types/error-constants.js';
|
|
7
|
+
import { EmailTemplate } from '@/types/email.js';
|
|
8
|
+
import { SendRawEmailRequest } from '@insforge/shared-schemas';
|
|
9
|
+
import { EmailProvider } from './base.provider.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Cloud email provider for sending emails via Insforge cloud backend
|
|
13
|
+
*/
|
|
14
|
+
export class CloudEmailProvider implements EmailProvider {
|
|
15
|
+
/**
|
|
16
|
+
* Generate JWT sign token for cloud API authentication
|
|
17
|
+
* @returns JWT token signed with project secret
|
|
18
|
+
*/
|
|
19
|
+
private generateSignToken(): string {
|
|
20
|
+
const projectId = config.cloud.projectId;
|
|
21
|
+
const jwtSecret = config.app.jwtSecret;
|
|
22
|
+
|
|
23
|
+
if (!projectId || projectId === 'local') {
|
|
24
|
+
throw new AppError(
|
|
25
|
+
'PROJECT_ID is not configured. Cannot send emails without cloud project setup.',
|
|
26
|
+
500,
|
|
27
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!jwtSecret) {
|
|
32
|
+
throw new AppError(
|
|
33
|
+
'JWT_SECRET is not configured. Cannot generate sign token.',
|
|
34
|
+
500,
|
|
35
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const payload = {
|
|
40
|
+
sub: projectId,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return jwt.sign(payload, jwtSecret, {
|
|
44
|
+
expiresIn: '10m', // Short-lived token for API request
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if provider supports templates
|
|
50
|
+
*/
|
|
51
|
+
supportsTemplates(): boolean {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Send email using predefined template
|
|
57
|
+
* @param email - Recipient email address
|
|
58
|
+
* @param name - Recipient name
|
|
59
|
+
* @param template - Template type (email-verification or reset-password)
|
|
60
|
+
* @param variables - Variables to use in the email template
|
|
61
|
+
* @returns Promise that resolves when email is sent successfully
|
|
62
|
+
*/
|
|
63
|
+
async sendWithTemplate(
|
|
64
|
+
email: string,
|
|
65
|
+
name: string,
|
|
66
|
+
template: EmailTemplate,
|
|
67
|
+
variables?: Record<string, string>
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
try {
|
|
70
|
+
const projectId = config.cloud.projectId;
|
|
71
|
+
const apiHost = config.cloud.apiHost;
|
|
72
|
+
const signToken = this.generateSignToken();
|
|
73
|
+
|
|
74
|
+
// Validate inputs
|
|
75
|
+
if (!email || !name || !template) {
|
|
76
|
+
throw new AppError(
|
|
77
|
+
'Missing required parameters for sending email',
|
|
78
|
+
400,
|
|
79
|
+
ERROR_CODES.INVALID_INPUT
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const validTemplates: EmailTemplate[] = [
|
|
84
|
+
'email-verification-code',
|
|
85
|
+
'email-verification-link',
|
|
86
|
+
'reset-password-code',
|
|
87
|
+
'reset-password-link',
|
|
88
|
+
];
|
|
89
|
+
if (!validTemplates.includes(template)) {
|
|
90
|
+
throw new AppError(
|
|
91
|
+
`Invalid template type: ${template}. Must be one of: ${validTemplates.join(', ')}`,
|
|
92
|
+
400,
|
|
93
|
+
ERROR_CODES.INVALID_INPUT
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const url = `${apiHost}/email/v1/${projectId}/send-with-template`;
|
|
98
|
+
const response = await axios.post(
|
|
99
|
+
url,
|
|
100
|
+
{
|
|
101
|
+
email,
|
|
102
|
+
name,
|
|
103
|
+
template,
|
|
104
|
+
variables,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
headers: {
|
|
108
|
+
'Content-Type': 'application/json',
|
|
109
|
+
sign: signToken,
|
|
110
|
+
},
|
|
111
|
+
timeout: 10000, // 10 second timeout
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (response.data?.success) {
|
|
116
|
+
logger.info('Email sent successfully', {
|
|
117
|
+
projectId,
|
|
118
|
+
template,
|
|
119
|
+
});
|
|
120
|
+
} else {
|
|
121
|
+
throw new AppError(
|
|
122
|
+
'Email service returned unsuccessful response',
|
|
123
|
+
500,
|
|
124
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
// Handle axios errors
|
|
129
|
+
if (axios.isAxiosError(error)) {
|
|
130
|
+
const status = error.response?.status;
|
|
131
|
+
const message = error.response?.data?.message || error.message;
|
|
132
|
+
|
|
133
|
+
logger.error('Failed to send email via cloud backend', {
|
|
134
|
+
projectId: config.cloud.projectId,
|
|
135
|
+
template,
|
|
136
|
+
status,
|
|
137
|
+
message,
|
|
138
|
+
error: error.response?.data,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Provide more specific error messages
|
|
142
|
+
if (status === 401 || status === 403) {
|
|
143
|
+
throw new AppError(
|
|
144
|
+
'Authentication failed with cloud email service. Check PROJECT_ID and JWT_SECRET.',
|
|
145
|
+
status,
|
|
146
|
+
ERROR_CODES.AUTH_UNAUTHORIZED
|
|
147
|
+
);
|
|
148
|
+
} else if (status === 429) {
|
|
149
|
+
throw new AppError(
|
|
150
|
+
'Email rate limit exceeded. Free plans are limited to 3000 emails per month.',
|
|
151
|
+
status,
|
|
152
|
+
ERROR_CODES.RATE_LIMITED
|
|
153
|
+
);
|
|
154
|
+
} else if (status === 400) {
|
|
155
|
+
throw new AppError(
|
|
156
|
+
`Invalid email request: ${message}`,
|
|
157
|
+
status,
|
|
158
|
+
ERROR_CODES.INVALID_INPUT
|
|
159
|
+
);
|
|
160
|
+
} else {
|
|
161
|
+
throw new AppError(
|
|
162
|
+
`Failed to send email: ${message}`,
|
|
163
|
+
status || 500,
|
|
164
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Re-throw AppError
|
|
170
|
+
if (error instanceof AppError) {
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Handle other errors
|
|
175
|
+
logger.error('Unexpected error sending email', {
|
|
176
|
+
projectId: config.cloud.projectId,
|
|
177
|
+
template,
|
|
178
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
throw new AppError(
|
|
182
|
+
`Failed to send email: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
183
|
+
500,
|
|
184
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Send custom/raw email via cloud backend
|
|
191
|
+
*/
|
|
192
|
+
async sendRaw(options: SendRawEmailRequest): Promise<void> {
|
|
193
|
+
try {
|
|
194
|
+
const projectId = config.cloud.projectId;
|
|
195
|
+
const apiHost = config.cloud.apiHost;
|
|
196
|
+
const signToken = this.generateSignToken();
|
|
197
|
+
|
|
198
|
+
const url = `${apiHost}/email/v1/${projectId}/send-on-demand`;
|
|
199
|
+
const response = await axios.post(url, options, {
|
|
200
|
+
headers: {
|
|
201
|
+
'Content-Type': 'application/json',
|
|
202
|
+
sign: signToken,
|
|
203
|
+
},
|
|
204
|
+
timeout: 10000,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
if (response.data?.success) {
|
|
208
|
+
logger.info('Raw email sent successfully', { projectId });
|
|
209
|
+
} else {
|
|
210
|
+
throw new AppError(
|
|
211
|
+
'Email service returned unsuccessful response',
|
|
212
|
+
500,
|
|
213
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (axios.isAxiosError(error)) {
|
|
218
|
+
const status = error.response?.status;
|
|
219
|
+
const message = error.response?.data?.message || error.message;
|
|
220
|
+
|
|
221
|
+
logger.error('Failed to send raw email via cloud backend', {
|
|
222
|
+
projectId: config.cloud.projectId,
|
|
223
|
+
status,
|
|
224
|
+
message,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (status === 401) {
|
|
228
|
+
throw new AppError(
|
|
229
|
+
'Authentication failed with cloud email service.',
|
|
230
|
+
status,
|
|
231
|
+
ERROR_CODES.AUTH_UNAUTHORIZED
|
|
232
|
+
);
|
|
233
|
+
} else if (status === 403) {
|
|
234
|
+
throw new AppError(
|
|
235
|
+
'Custom email service is not available for free plan. Please upgrade to use this feature.',
|
|
236
|
+
status,
|
|
237
|
+
ERROR_CODES.FORBIDDEN
|
|
238
|
+
);
|
|
239
|
+
} else if (status === 429) {
|
|
240
|
+
throw new AppError(
|
|
241
|
+
'Email rate limit exceeded. Starter plan is limited 10 emails per hour, and Pro plan is limited 50 emails per hour',
|
|
242
|
+
status,
|
|
243
|
+
ERROR_CODES.RATE_LIMITED
|
|
244
|
+
);
|
|
245
|
+
} else if (status === 400) {
|
|
246
|
+
throw new AppError(
|
|
247
|
+
`Invalid email request: ${message}`,
|
|
248
|
+
status,
|
|
249
|
+
ERROR_CODES.INVALID_INPUT
|
|
250
|
+
);
|
|
251
|
+
} else {
|
|
252
|
+
throw new AppError(
|
|
253
|
+
`Failed to send email: ${message}`,
|
|
254
|
+
status || 500,
|
|
255
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (error instanceof AppError) {
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
throw new AppError(
|
|
265
|
+
`Failed to send email: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
266
|
+
500,
|
|
267
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|