insforge 0.3.2 → 1.2.10
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/.cursor/rules/cursor-rules.mdc +94 -0
- package/.dockerignore +3 -0
- package/.env.example +33 -4
- package/.github/ISSUE_TEMPLATE/bug_report.yml +13 -60
- package/.github/ISSUE_TEMPLATE/config.yml +2 -2
- package/.github/ISSUE_TEMPLATE/feature_request.yml +10 -63
- package/.github/PULL_REQUEST_TEMPLATE.md +7 -0
- package/.github/workflows/build-image.yml +2 -1
- package/.github/workflows/e2e.yml +63 -0
- package/CHANGELOG.md +41 -0
- package/CLAUDE_PLUGIN.md +104 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +1 -1
- package/Dockerfile +4 -1
- package/README.md +66 -18
- 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 +115 -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 +57 -0
- package/auth/src/pages/SignUpPage.tsx +57 -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 +9 -9
- 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} +20 -24
- package/backend/src/api/routes/auth/index.routes.ts +570 -0
- package/backend/src/api/routes/auth/oauth.routes.ts +448 -0
- package/backend/src/api/routes/{database.advance.ts → database/advance.routes.ts} +107 -65
- package/backend/src/api/routes/database/index.routes.ts +13 -0
- package/backend/src/api/routes/{database.records.ts → database/records.routes.ts} +22 -8
- package/backend/src/api/routes/{database.tables.ts → database/tables.routes.ts} +20 -23
- package/backend/src/api/routes/docs/index.routes.ts +76 -0
- package/backend/src/api/routes/functions/index.routes.ts +188 -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} +21 -31
- package/backend/src/api/routes/{secrets.ts → secrets/index.routes.ts} +27 -22
- package/backend/src/api/routes/{storage.ts → storage/index.routes.ts} +34 -53
- package/backend/src/api/routes/usage/index.routes.ts +89 -0
- package/backend/src/infra/config/app.config.ts +51 -0
- package/backend/src/{core/database/manager.ts → infra/database/database.manager.ts} +76 -85
- 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/{core/secrets/encryption.ts → infra/security/encryption.manager.ts} +3 -2
- package/backend/src/infra/security/token.manager.ts +125 -0
- package/backend/src/{core/socket/socket.ts → infra/socket/socket.manager.ts} +15 -15
- package/backend/src/providers/ai/openrouter.provider.ts +377 -0
- package/backend/src/providers/email/base.provider.ts +41 -0
- package/backend/src/providers/email/cloud.provider.ts +187 -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/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 +7 -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 +70 -74
- 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 +1136 -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 +811 -0
- package/backend/src/services/email/email.service.ts +75 -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/{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 +66 -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/{core/socket/types.ts → types/socket.ts} +5 -6
- package/backend/src/utils/environment.ts +9 -3
- package/backend/src/utils/logger.ts +20 -2
- package/backend/src/utils/seed.ts +150 -57
- package/backend/src/utils/sql-parser.ts +1 -1
- package/backend/src/utils/utils.ts +114 -0
- package/backend/src/utils/validations.ts +40 -4
- 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 +1 -1
- package/backend/tests/local/test-e2e.sh +1 -1
- package/backend/tests/local/test-functions.sh +123 -0
- package/backend/tests/local/test-logs.sh +132 -0
- package/backend/tests/local/test-public-bucket.sh +3 -3
- package/backend/tests/local/test-secrets.sh +14 -12
- package/backend/tests/local/test-traditional-rest.sh +2 -2
- package/backend/tests/manual/test-rawsql-modes.sh +244 -0
- package/backend/tests/test-config.sh +37 -1
- 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 +1 -1
- 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 +60 -4
- package/docker-compose.yml +65 -4
- package/docker-init/db/db-init.sql +6 -34
- package/docker-init/logs/vector.yml +236 -0
- package/docs/README.md +44 -0
- package/docs/changelog.mdx +67 -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/functions/architecture.mdx +105 -0
- package/docs/core-concepts/functions/sdk.mdx +184 -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/docs.json +210 -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/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 +55 -374
- package/docs/introduction.mdx +45 -0
- package/docs/logo/dark.svg +22 -0
- package/docs/logo/light.svg +20 -0
- package/docs/partnership.mdx +647 -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/frontend/package.json +10 -4
- package/frontend/src/App.tsx +13 -82
- package/frontend/src/assets/icons/connected.svg +3 -0
- package/frontend/src/assets/icons/loader.svg +9 -0
- package/frontend/src/assets/logos/apple.svg +4 -0
- package/frontend/src/assets/logos/discord.svg +1 -1
- package/frontend/src/assets/logos/facebook.svg +3 -0
- 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/spotify.svg +17 -0
- package/frontend/src/assets/logos/tiktok.svg +6 -0
- 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 +133 -92
- 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/page/AIPage.tsx +67 -79
- 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 +53 -30
- package/frontend/src/features/auth/components/UserFormDialog.tsx +13 -6
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +44 -14
- package/frontend/src/features/auth/components/index.ts +5 -0
- package/frontend/src/features/auth/helpers.tsx +200 -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/page/AuthMethodsPage.tsx +275 -0
- package/frontend/src/features/auth/page/ConfigurationPage.tsx +395 -0
- package/frontend/src/features/auth/page/UsersPage.tsx +285 -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/page/DashboardPage.tsx +187 -169
- 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/TableEmptyState.tsx +6 -5
- package/frontend/src/features/database/components/TableForm.tsx +28 -15
- 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/useFullMetadata.ts +18 -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 +131 -0
- package/frontend/src/features/database/index.ts +6 -1
- package/frontend/src/features/database/page/FunctionsPage.tsx +211 -0
- package/frontend/src/features/database/page/IndexesPage.tsx +240 -0
- package/frontend/src/features/database/page/PoliciesPage.tsx +248 -0
- package/frontend/src/features/database/page/SQLEditorPage.tsx +382 -0
- package/frontend/src/features/database/page/{DatabasePage.tsx → TablesPage.tsx} +186 -185
- package/frontend/src/features/database/page/TemplatesPage.tsx +39 -0
- package/frontend/src/features/database/page/TriggersPage.tsx +242 -0
- package/frontend/src/features/database/services/advance.service.ts +66 -0
- package/frontend/src/features/database/services/{database.service.ts → record.service.ts} +67 -64
- package/frontend/src/features/database/services/table.service.ts +64 -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/page/FunctionsPage.tsx +160 -17
- package/frontend/src/features/functions/{components/SecretsContent.tsx → page/SecretsPage.tsx} +8 -12
- 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/page/CloudLoginPage.tsx +79 -54
- package/frontend/src/features/login/page/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 +181 -0
- package/frontend/src/features/logs/index.ts +8 -2
- package/frontend/src/features/logs/page/AuditsPage.tsx +91 -38
- package/frontend/src/features/logs/page/LogsPage.tsx +152 -0
- package/frontend/src/features/logs/page/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/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/StoragePage.tsx +41 -115
- 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/page/VisualizerPage.tsx +33 -29
- 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 +5 -6
- 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 +84 -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 +183 -0
- package/frontend/src/lib/utils/{validation-schemas.ts → schemaValidations.ts} +10 -5
- package/frontend/src/lib/utils/utils.ts +19 -1
- package/frontend/src/vite-env.d.ts +1 -0
- package/frontend/vite.config.ts +5 -3
- package/functions/server.ts +28 -3
- 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 +31 -4
- package/openapi/auth.yaml +827 -146
- package/package.json +16 -7
- package/shared-schemas/package.json +1 -1
- package/shared-schemas/src/ai-api.schema.ts +34 -58
- package/shared-schemas/src/ai.schema.ts +5 -0
- package/shared-schemas/src/auth-api.schema.ts +154 -8
- package/shared-schemas/src/auth.schema.ts +42 -6
- package/shared-schemas/src/cloud-events.schema.ts +57 -0
- package/shared-schemas/src/database-api.schema.ts +3 -3
- package/shared-schemas/src/database.schema.ts +1 -1
- package/shared-schemas/src/index.ts +1 -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 +9 -4
- package/test-gemini.sh +35 -0
- package/test-usage-admin.sh +57 -0
- package/test-usage.sh +50 -0
- 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 -781
- 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/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/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/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/login/components/AuthErrorBoundary.tsx +0 -87
- package/frontend/src/features/login/components/PrivateRoute.tsx +0 -24
- 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/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/backend/{migrations → src/infra/database/migrations}/000_create-base-tables.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/001_create-helper-functions.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/002_rename-auth-tables.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/003_create-users-table.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/004_add-reload-postgrest-func.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/005_enable-project-admin-modify-users.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/006_modify-ai-usage-table.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/007_drop-metadata-table.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/008_add-system-tables.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/009_add-function-secrets.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/010_modify-ai-config-modalities.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/011_refactor-secrets-table.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/012_add-storage-uploaded-by.sql +0 -0
- /package/frontend/src/{features/metadata → lib}/services/metadata.service.ts +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
-- Migration: 015 - Create email OTP verification table and email auth configs
|
|
2
|
+
-- This migration creates:
|
|
3
|
+
-- 1. _email_otps: Stores one-time tokens for email verification purposes
|
|
4
|
+
-- - Supports both short numeric codes (6 digits) for manual entry
|
|
5
|
+
-- - Supports long cryptographic tokens (64 chars) for magic links
|
|
6
|
+
-- - Uses dual hashing strategy:
|
|
7
|
+
-- * NUMERIC_CODE (6 digits): Bcrypt hash (slow, defense against brute force)
|
|
8
|
+
-- * LINK_TOKEN (64 hex chars): SHA-256 hash (fast, enables direct O(1) lookup)
|
|
9
|
+
-- 2. _auth_configs: Stores email authentication configuration (single-row table)
|
|
10
|
+
|
|
11
|
+
-- 1. Create email OTP verification table
|
|
12
|
+
CREATE TABLE IF NOT EXISTS _email_otps (
|
|
13
|
+
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
14
|
+
email TEXT NOT NULL,
|
|
15
|
+
purpose TEXT NOT NULL,
|
|
16
|
+
otp_hash TEXT NOT NULL, -- Hash of OTP: bcrypt for NUMERIC_CODE, SHA-256 for LINK_TOKEN
|
|
17
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
18
|
+
consumed_at TIMESTAMPTZ,
|
|
19
|
+
attempts_count INTEGER DEFAULT 0 NOT NULL,
|
|
20
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
21
|
+
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
22
|
+
UNIQUE (email, purpose) -- Only one active token per email/purpose combination
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
-- Create indexes for better query performance
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_email_otps_email_purpose ON _email_otps(email, purpose);
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_email_otps_expires_at ON _email_otps(expires_at);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_email_otps_otp_hash ON _email_otps(otp_hash); -- For direct LINK_TOKEN lookup
|
|
29
|
+
|
|
30
|
+
-- Add trigger for updated_at
|
|
31
|
+
DROP TRIGGER IF EXISTS update__email_otps_updated_at ON _email_otps;
|
|
32
|
+
CREATE TRIGGER update__email_otps_updated_at
|
|
33
|
+
BEFORE UPDATE ON _email_otps
|
|
34
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
35
|
+
|
|
36
|
+
-- 2. Create email authentication configuration table (single-row design)
|
|
37
|
+
-- This table stores global email authentication settings for the project
|
|
38
|
+
CREATE TABLE IF NOT EXISTS _auth_configs (
|
|
39
|
+
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
40
|
+
require_email_verification BOOLEAN DEFAULT FALSE NOT NULL,
|
|
41
|
+
password_min_length INTEGER DEFAULT 6 NOT NULL CHECK (password_min_length >= 4 AND password_min_length <= 128),
|
|
42
|
+
require_number BOOLEAN DEFAULT FALSE NOT NULL,
|
|
43
|
+
require_lowercase BOOLEAN DEFAULT FALSE NOT NULL,
|
|
44
|
+
require_uppercase BOOLEAN DEFAULT FALSE NOT NULL,
|
|
45
|
+
require_special_char BOOLEAN DEFAULT FALSE NOT NULL,
|
|
46
|
+
verify_email_redirect_to TEXT, -- Custom URL to redirect after successful email verification (defaults to no redirect if NULL)
|
|
47
|
+
reset_password_redirect_to TEXT, -- Custom URL to redirect after successful password reset (defaults to no redirect if NULL)
|
|
48
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
49
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
-- Ensure only one row exists (singleton pattern)
|
|
53
|
+
-- This constraint prevents multiple configuration rows
|
|
54
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_auth_configs_singleton ON _auth_configs ((1));
|
|
55
|
+
|
|
56
|
+
-- Add trigger for updated_at
|
|
57
|
+
DROP TRIGGER IF EXISTS update__auth_configs_updated_at ON _auth_configs;
|
|
58
|
+
CREATE TRIGGER update__auth_configs_updated_at
|
|
59
|
+
BEFORE UPDATE ON _auth_configs
|
|
60
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- Migration 016: Update _email_otps and _auth_configs tables
|
|
2
|
+
--
|
|
3
|
+
-- Changes:
|
|
4
|
+
-- 1. _email_otps table:
|
|
5
|
+
-- - Remove attempts_count column (brute force protection moved to API rate limiter)
|
|
6
|
+
-- 2. _auth_configs table:
|
|
7
|
+
-- - Remove verify_email_redirect_to and reset_password_redirect_to columns
|
|
8
|
+
-- - Add verify_email_method and reset_password_method columns (code or link)
|
|
9
|
+
-- - Add sign_in_redirect_to column
|
|
10
|
+
|
|
11
|
+
-- Update _email_otps: Remove attempts_count column
|
|
12
|
+
ALTER TABLE _email_otps DROP COLUMN IF EXISTS attempts_count;
|
|
13
|
+
|
|
14
|
+
-- Update _auth_configs: Remove old redirect columns
|
|
15
|
+
ALTER TABLE _auth_configs
|
|
16
|
+
DROP COLUMN IF EXISTS verify_email_redirect_to,
|
|
17
|
+
DROP COLUMN IF EXISTS reset_password_redirect_to;
|
|
18
|
+
|
|
19
|
+
-- Add new columns to _auth_configs
|
|
20
|
+
-- Note: DEFAULT 'code' NOT NULL ensures existing rows automatically get 'code' value
|
|
21
|
+
ALTER TABLE _auth_configs
|
|
22
|
+
ADD COLUMN IF NOT EXISTS verify_email_method TEXT DEFAULT 'code' NOT NULL CHECK (verify_email_method IN ('code', 'link')),
|
|
23
|
+
ADD COLUMN IF NOT EXISTS reset_password_method TEXT DEFAULT 'code' NOT NULL CHECK (reset_password_method IN ('code', 'link')),
|
|
24
|
+
ADD COLUMN IF NOT EXISTS sign_in_redirect_to TEXT;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* EncryptionManager - Handles encryption/decryption operations
|
|
5
|
+
* Infrastructure layer for secrets encryption
|
|
5
6
|
*/
|
|
6
|
-
export class
|
|
7
|
+
export class EncryptionManager {
|
|
7
8
|
private static encryptionKey: Buffer | null = null;
|
|
8
9
|
|
|
9
10
|
private static getEncryptionKey(): Buffer {
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
import { createRemoteJWKSet, JWTPayload, jwtVerify } from 'jose';
|
|
3
|
+
import { AppError } from '@/api/middlewares/error.js';
|
|
4
|
+
import { ERROR_CODES, NEXT_ACTION } from '@/types/error-constants.js';
|
|
5
|
+
import type { TokenPayloadSchema } from '@insforge/shared-schemas';
|
|
6
|
+
|
|
7
|
+
const JWT_SECRET = process.env.JWT_SECRET ?? '';
|
|
8
|
+
const JWT_EXPIRES_IN = '7d';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create JWKS instance with caching and timeout configuration
|
|
12
|
+
* The instance will automatically cache keys and handle refetching
|
|
13
|
+
*/
|
|
14
|
+
const cloudApiHost = process.env.CLOUD_API_HOST || 'https://api.insforge.dev';
|
|
15
|
+
const JWKS = createRemoteJWKSet(new URL(`${cloudApiHost}/.well-known/jwks.json`), {
|
|
16
|
+
timeoutDuration: 10000, // 10 second timeout for HTTP requests
|
|
17
|
+
cooldownDuration: 30000, // 30 seconds cooldown after successful fetch
|
|
18
|
+
cacheMaxAge: 600000, // Maximum 10 minutes between refetches
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* TokenManager - Handles JWT token operations
|
|
23
|
+
* Infrastructure layer for token generation and verification
|
|
24
|
+
*/
|
|
25
|
+
export class TokenManager {
|
|
26
|
+
private static instance: TokenManager;
|
|
27
|
+
|
|
28
|
+
private constructor() {
|
|
29
|
+
if (!process.env.JWT_SECRET) {
|
|
30
|
+
throw new Error('JWT_SECRET environment variable is required');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public static getInstance(): TokenManager {
|
|
35
|
+
if (!TokenManager.instance) {
|
|
36
|
+
TokenManager.instance = new TokenManager();
|
|
37
|
+
}
|
|
38
|
+
return TokenManager.instance;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate JWT token for users and admins
|
|
43
|
+
*/
|
|
44
|
+
generateToken(payload: TokenPayloadSchema): string {
|
|
45
|
+
return jwt.sign(payload, JWT_SECRET, {
|
|
46
|
+
algorithm: 'HS256',
|
|
47
|
+
expiresIn: JWT_EXPIRES_IN,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generate anonymous JWT token (never expires)
|
|
53
|
+
*/
|
|
54
|
+
generateAnonToken(): string {
|
|
55
|
+
const payload = {
|
|
56
|
+
sub: '12345678-1234-5678-90ab-cdef12345678',
|
|
57
|
+
email: 'anon@insforge.com',
|
|
58
|
+
role: 'anon',
|
|
59
|
+
};
|
|
60
|
+
return jwt.sign(payload, JWT_SECRET, {
|
|
61
|
+
algorithm: 'HS256',
|
|
62
|
+
// No expiresIn means token never expires
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Verify JWT token
|
|
68
|
+
*/
|
|
69
|
+
verifyToken(token: string): TokenPayloadSchema {
|
|
70
|
+
try {
|
|
71
|
+
const decoded = jwt.verify(token, JWT_SECRET) as TokenPayloadSchema;
|
|
72
|
+
return {
|
|
73
|
+
sub: decoded.sub,
|
|
74
|
+
email: decoded.email,
|
|
75
|
+
role: decoded.role || 'authenticated',
|
|
76
|
+
};
|
|
77
|
+
} catch {
|
|
78
|
+
throw new AppError('Invalid token', 401, ERROR_CODES.AUTH_UNAUTHORIZED);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Verify cloud backend JWT token
|
|
84
|
+
* Validates JWT tokens from api.insforge.dev using JWKS
|
|
85
|
+
*/
|
|
86
|
+
async verifyCloudToken(token: string): Promise<{ projectId: string; payload: JWTPayload }> {
|
|
87
|
+
try {
|
|
88
|
+
// JWKS handles caching internally, no need to manage it manually
|
|
89
|
+
const { payload } = await jwtVerify(token, JWKS, {
|
|
90
|
+
algorithms: ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512'],
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Verify project_id matches if configured
|
|
94
|
+
const tokenProjectId = payload['projectId'] as string;
|
|
95
|
+
const expectedProjectId = process.env.PROJECT_ID;
|
|
96
|
+
|
|
97
|
+
if (expectedProjectId && tokenProjectId !== expectedProjectId) {
|
|
98
|
+
throw new AppError(
|
|
99
|
+
'Project ID mismatch',
|
|
100
|
+
403,
|
|
101
|
+
ERROR_CODES.AUTH_UNAUTHORIZED,
|
|
102
|
+
NEXT_ACTION.CHECK_TOKEN
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
projectId: tokenProjectId || expectedProjectId || 'local',
|
|
108
|
+
payload,
|
|
109
|
+
};
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// Re-throw AppError as-is
|
|
112
|
+
if (error instanceof AppError) {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Wrap other JWT errors
|
|
117
|
+
throw new AppError(
|
|
118
|
+
`Invalid cloud authorization code: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
119
|
+
401,
|
|
120
|
+
ERROR_CODES.AUTH_INVALID_CREDENTIALS,
|
|
121
|
+
NEXT_ACTION.CHECK_TOKEN
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Server as HttpServer } from 'http';
|
|
2
2
|
import { Server as SocketIOServer, Socket } from 'socket.io';
|
|
3
3
|
import logger from '@/utils/logger.js';
|
|
4
|
-
import {
|
|
4
|
+
import { TokenManager } from '@/infra/security/token.manager.js';
|
|
5
5
|
import {
|
|
6
6
|
ServerEvents,
|
|
7
7
|
ClientEvents,
|
|
@@ -10,31 +10,31 @@ import {
|
|
|
10
10
|
NotificationPayload,
|
|
11
11
|
SubscribePayload,
|
|
12
12
|
UnsubscribePayload,
|
|
13
|
-
} from '
|
|
14
|
-
import { AppError } from '@/api/
|
|
13
|
+
} from '@/types/socket.js';
|
|
14
|
+
import { AppError } from '@/api/middlewares/error.js';
|
|
15
15
|
import { ERROR_CODES, NEXT_ACTION } from '@/types/error-constants.js';
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const tokenManager = TokenManager.getInstance();
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* SocketManager - Industrial-grade Socket.IO implementation
|
|
21
|
+
* Infrastructure layer for real-time WebSocket communication
|
|
22
22
|
*/
|
|
23
|
-
export class
|
|
24
|
-
private static instance:
|
|
23
|
+
export class SocketManager {
|
|
24
|
+
private static instance: SocketManager;
|
|
25
25
|
private io: SocketIOServer | null = null;
|
|
26
26
|
private socketMetadata: Map<string, SocketMetadata> = new Map();
|
|
27
27
|
|
|
28
28
|
private constructor() {}
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
* Singleton pattern for global socket
|
|
31
|
+
* Singleton pattern for global socket manager access
|
|
32
32
|
*/
|
|
33
|
-
static getInstance():
|
|
34
|
-
if (!
|
|
35
|
-
|
|
33
|
+
static getInstance(): SocketManager {
|
|
34
|
+
if (!SocketManager.instance) {
|
|
35
|
+
SocketManager.instance = new SocketManager();
|
|
36
36
|
}
|
|
37
|
-
return
|
|
37
|
+
return SocketManager.instance;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -66,7 +66,7 @@ export class SocketService {
|
|
|
66
66
|
this.io.use((socket, next) => {
|
|
67
67
|
try {
|
|
68
68
|
const token = socket.handshake.auth.token;
|
|
69
|
-
const payload =
|
|
69
|
+
const payload = tokenManager.verifyToken(token);
|
|
70
70
|
if (!payload.role) {
|
|
71
71
|
throw new AppError(
|
|
72
72
|
'Invalid token: missing role',
|
|
@@ -385,4 +385,4 @@ export class SocketService {
|
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
// Export singleton instance for convenience
|
|
388
|
-
export const socketService =
|
|
388
|
+
export const socketService = SocketManager.getInstance();
|
|
@@ -0,0 +1,377 @@
|
|
|
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 AIClientService {
|
|
45
|
+
private static instance: AIClientService;
|
|
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(): AIClientService {
|
|
55
|
+
if (!AIClientService.instance) {
|
|
56
|
+
AIClientService.instance = new AIClientService();
|
|
57
|
+
}
|
|
58
|
+
return AIClientService.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
|
+
* This is the main method services should use
|
|
104
|
+
*/
|
|
105
|
+
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
|
+
// Retry with exponential backoff (3 attempts)
|
|
353
|
+
const maxRetries = 3;
|
|
354
|
+
|
|
355
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
356
|
+
try {
|
|
357
|
+
const backoffMs = Math.pow(2, attempt - 1) * 1000; // 1s, 2s, 4s
|
|
358
|
+
logger.info(
|
|
359
|
+
`Retrying request after renewal (attempt ${attempt}/${maxRetries}), waiting ${backoffMs}ms...`
|
|
360
|
+
);
|
|
361
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
362
|
+
|
|
363
|
+
const result = await request(client);
|
|
364
|
+
logger.info('Request succeeded after API key renewal');
|
|
365
|
+
return result;
|
|
366
|
+
} catch (retryError) {
|
|
367
|
+
if (attempt === maxRetries) {
|
|
368
|
+
logger.error(`All ${maxRetries} retry attempts failed after API key renewal`);
|
|
369
|
+
throw retryError;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|