insforge 1.2.10 → 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 -20
- package/.dockerignore +60 -60
- package/.env.example +83 -77
- package/.github/ISSUE_TEMPLATE/bug_report.yml +36 -36
- package/.github/ISSUE_TEMPLATE/config.yml +11 -11
- package/.github/ISSUE_TEMPLATE/feature_request.yml +26 -26
- package/.github/PULL_REQUEST_TEMPLATE.md +7 -7
- package/.github/copilot-instructions.md +146 -146
- package/.github/workflows/build-image.yml +65 -65
- package/.github/workflows/ci-premerge-check.yml +23 -23
- package/.github/workflows/e2e.yml +63 -63
- package/.github/workflows/lint-and-format.yml +32 -32
- package/.prettierignore +64 -64
- package/CHANGELOG.md +44 -44
- package/CLAUDE_PLUGIN.md +104 -104
- package/CODE_OF_CONDUCT.md +128 -128
- package/CONTRIBUTING.md +125 -125
- package/Dockerfile +30 -30
- package/GITHUB_OAUTH_SETUP.md +49 -49
- package/GOOGLE_OAUTH_SETUP.md +148 -148
- package/LICENSE +201 -201
- package/README.md +182 -182
- package/assets/Dark.svg +23 -23
- package/auth/package.json +28 -28
- package/auth/src/lib/broadcastService.ts +117 -115
- package/auth/src/pages/SignInPage.tsx +60 -57
- package/auth/src/pages/SignUpPage.tsx +60 -57
- package/auth/tsconfig.json +32 -32
- package/auth/tsconfig.node.json +11 -11
- package/backend/package.json +78 -75
- package/backend/src/api/routes/ai/index.routes.ts +3 -3
- package/backend/src/api/routes/auth/index.routes.ts +667 -570
- package/backend/src/api/routes/auth/oauth.routes.ts +473 -448
- package/backend/src/api/routes/database/advance.routes.ts +37 -16
- package/backend/src/api/routes/database/index.routes.ts +78 -1
- package/backend/src/api/routes/database/records.routes.ts +10 -10
- package/backend/src/api/routes/database/tables.routes.ts +0 -14
- package/backend/src/api/routes/docs/index.routes.ts +75 -76
- package/backend/src/api/routes/email/index.routes.ts +35 -0
- package/backend/src/api/routes/functions/index.routes.ts +18 -12
- package/backend/src/api/routes/metadata/index.routes.ts +12 -0
- package/backend/src/api/routes/realtime/channels.routes.ts +81 -0
- package/backend/src/api/routes/realtime/index.routes.ts +12 -0
- package/backend/src/api/routes/realtime/messages.routes.ts +48 -0
- package/backend/src/api/routes/realtime/permissions.routes.ts +19 -0
- package/backend/src/api/routes/storage/index.routes.ts +18 -12
- package/backend/src/api/routes/usage/index.routes.ts +6 -4
- package/backend/src/infra/database/database.manager.ts +14 -1
- package/backend/src/infra/database/migrations/000_create-base-tables.sql +141 -141
- package/backend/src/infra/database/migrations/001_create-helper-functions.sql +40 -40
- package/backend/src/infra/database/migrations/002_rename-auth-tables.sql +29 -29
- package/backend/src/infra/database/migrations/003_create-users-table.sql +55 -55
- package/backend/src/infra/database/migrations/004_add-reload-postgrest-func.sql +23 -23
- package/backend/src/infra/database/migrations/005_enable-project-admin-modify-users.sql +29 -29
- package/backend/src/infra/database/migrations/006_modify-ai-usage-table.sql +24 -24
- package/backend/src/infra/database/migrations/007_drop-metadata-table.sql +1 -1
- package/backend/src/infra/database/migrations/008_add-system-tables.sql +76 -76
- package/backend/src/infra/database/migrations/009_add-function-secrets.sql +23 -23
- package/backend/src/infra/database/migrations/010_modify-ai-config-modalities.sql +93 -93
- package/backend/src/infra/database/migrations/011_refactor-secrets-table.sql +15 -15
- package/backend/src/infra/database/migrations/012_add-storage-uploaded-by.sql +7 -7
- package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -44
- package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +7 -7
- package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +59 -59
- package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -24
- package/backend/src/infra/database/migrations/017_create-realtime-schema.sql +233 -0
- package/backend/src/infra/realtime/realtime.manager.ts +246 -0
- package/backend/src/infra/realtime/webhook-sender.ts +82 -0
- package/backend/src/infra/security/token.manager.ts +219 -125
- package/backend/src/infra/socket/socket.manager.ts +198 -64
- package/backend/src/providers/ai/openrouter.provider.ts +12 -9
- package/backend/src/providers/email/base.provider.ts +4 -7
- package/backend/src/providers/email/cloud.provider.ts +84 -0
- package/backend/src/providers/oauth/apple.provider.ts +266 -0
- package/backend/src/providers/oauth/index.ts +1 -0
- package/backend/src/server.ts +317 -284
- package/backend/src/services/ai/ai-model.service.ts +5 -5
- package/backend/src/services/ai/chat-completion.service.ts +4 -4
- package/backend/src/services/ai/image-generation.service.ts +3 -3
- package/backend/src/services/auth/auth.service.ts +14 -0
- package/backend/src/services/database/database-table.service.ts +0 -9
- package/backend/src/services/database/database.service.ts +127 -0
- package/backend/src/services/email/email.service.ts +5 -7
- 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/types/auth.ts +11 -0
- package/backend/src/types/realtime.ts +18 -0
- package/backend/src/types/socket.ts +7 -31
- package/backend/src/utils/cookies.ts +35 -0
- package/backend/src/utils/s3-config-loader.ts +64 -0
- package/backend/src/utils/seed.ts +301 -298
- package/backend/src/utils/sql-parser.ts +90 -0
- package/backend/tests/README.md +133 -133
- package/backend/tests/cleanup-all-test-data.sh +230 -230
- package/backend/tests/cloud/test-s3-multitenant.sh +131 -131
- package/backend/tests/local/comprehensive-curl-tests.sh +155 -155
- package/backend/tests/local/test-ai-config.sh +129 -129
- package/backend/tests/local/test-ai-usage.sh +80 -80
- package/backend/tests/local/test-auth-router.sh +143 -143
- package/backend/tests/local/test-database-router.sh +222 -222
- package/backend/tests/local/test-e2e.sh +240 -240
- package/backend/tests/local/test-fk-errors.sh +96 -96
- package/backend/tests/local/test-functions.sh +123 -123
- package/backend/tests/local/test-id-field.sh +200 -200
- package/backend/tests/local/test-logs.sh +132 -132
- package/backend/tests/local/test-public-bucket.sh +264 -264
- package/backend/tests/local/test-secrets.sh +249 -249
- package/backend/tests/local/test-serverless-functions.sh.disabled +325 -325
- package/backend/tests/local/test-traditional-rest.sh +208 -208
- package/backend/tests/manual/README.md +50 -50
- package/backend/tests/manual/create-large-table-simple.sql +10 -10
- package/backend/tests/manual/seed-large-table.sql +100 -100
- package/backend/tests/manual/setup-large-table-extras.sql +33 -33
- package/backend/tests/manual/test-bulk-upsert.sh +409 -409
- package/backend/tests/manual/test-database-advance.sh +296 -296
- package/backend/tests/manual/test-postgrest-stability.sh +191 -191
- package/backend/tests/manual/test-rawsql-export-import.sh +411 -411
- package/backend/tests/manual/test-rawsql-modes.sh +244 -244
- package/backend/tests/manual/test-universal-storage.sh +263 -263
- package/backend/tests/manual/test-users.sql +17 -17
- package/backend/tests/run-all-tests.sh +139 -139
- package/backend/tests/setup.ts +0 -0
- package/backend/tests/test-config.sh +338 -338
- package/backend/tests/unit/analyze-query.test.ts +697 -0
- package/backend/tsconfig.json +22 -22
- package/claude-plugin/.claude-plugin/plugin.json +24 -24
- package/claude-plugin/README.md +133 -133
- package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +270 -270
- package/docker-compose.prod.yml +204 -200
- package/docker-compose.yml +232 -228
- package/docker-init/db/db-init.sql +97 -97
- package/docker-init/db/jwt.sql +5 -5
- package/docker-init/db/postgresql.conf +16 -16
- package/docker-init/logs/vector.yml +236 -236
- package/docs/README.md +44 -44
- package/docs/agent-docs/real-time.md +269 -0
- package/docs/changelog.mdx +119 -67
- package/docs/core-concepts/ai/architecture.mdx +372 -372
- package/docs/core-concepts/ai/sdk.mdx +213 -213
- package/docs/core-concepts/authentication/architecture.mdx +278 -278
- package/docs/core-concepts/authentication/sdk.mdx +414 -414
- package/docs/core-concepts/authentication/ui-components/customization.mdx +529 -529
- package/docs/core-concepts/authentication/ui-components/nextjs.mdx +221 -221
- package/docs/core-concepts/authentication/ui-components/react-router.mdx +184 -184
- package/docs/core-concepts/authentication/ui-components/react.mdx +129 -129
- package/docs/core-concepts/database/architecture.mdx +255 -255
- package/docs/core-concepts/database/sdk.mdx +382 -382
- 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 -105
- package/docs/core-concepts/functions/sdk.mdx +184 -184
- package/docs/core-concepts/realtime/architecture.mdx +446 -0
- package/docs/core-concepts/realtime/sdk.mdx +409 -0
- package/docs/core-concepts/storage/architecture.mdx +243 -243
- package/docs/core-concepts/storage/sdk.mdx +253 -253
- package/docs/deployment/README.md +94 -94
- package/docs/deployment/deploy-to-aws-ec2.md +564 -564
- package/docs/deployment/deploy-to-azure-virtual-machines.md +312 -312
- package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -613
- package/docs/deployment/deploy-to-render.md +441 -441
- package/docs/deprecated/insforge-auth-api.md +214 -214
- package/docs/deprecated/insforge-auth-sdk.md +99 -99
- package/docs/deprecated/insforge-db-api.md +358 -358
- package/docs/deprecated/insforge-db-sdk.md +139 -139
- package/docs/deprecated/insforge-debug-sdk.md +156 -156
- package/docs/deprecated/insforge-debug.md +64 -64
- package/docs/deprecated/insforge-instructions.md +123 -123
- package/docs/deprecated/insforge-project.md +117 -117
- package/docs/deprecated/insforge-storage-api.md +278 -278
- package/docs/deprecated/insforge-storage-sdk.md +158 -158
- package/docs/docs.json +232 -210
- package/docs/examples/framework-guides/nextjs.mdx +131 -131
- package/docs/examples/framework-guides/nuxt.mdx +165 -165
- package/docs/examples/framework-guides/react.mdx +165 -165
- package/docs/examples/framework-guides/svelte.mdx +153 -153
- package/docs/examples/framework-guides/vue.mdx +159 -159
- package/docs/examples/overview.mdx +67 -67
- package/docs/favicon.svg +19 -19
- package/docs/images/changelog/dec-2025/ai-integration.png +0 -0
- package/docs/images/changelog/dec-2025/ai-models.webp +0 -0
- package/docs/images/changelog/dec-2025/alipay-payment.webp +0 -0
- package/docs/images/changelog/dec-2025/apple-login.jpg +0 -0
- package/docs/images/changelog/dec-2025/mcp-installer.png +0 -0
- package/docs/images/changelog/dec-2025/realtime-module.jpg +0 -0
- package/docs/images/icons/ai.svg +4 -4
- package/docs/images/logos/nextjs.svg +4 -4
- package/docs/images/logos/nuxt.svg +4 -4
- package/docs/images/logos/react.svg +5 -5
- package/docs/images/logos/svelte.svg +4 -4
- package/docs/images/logos/vue.svg +5 -5
- package/docs/insforge-instructions-sdk.md +89 -88
- package/docs/introduction.mdx +45 -45
- package/docs/logo/dark.svg +22 -22
- package/docs/logo/light.svg +20 -20
- package/docs/partnership.mdx +651 -646
- package/docs/quickstart.mdx +82 -82
- package/docs/showcase.mdx +52 -52
- package/docs/snippets/sdk-installation.mdx +21 -21
- package/docs/snippets/service-icons.mdx +27 -27
- package/examples/oauth/frontend-oauth-example.html +250 -250
- package/examples/response-examples.md +443 -443
- package/frontend/components.json +17 -17
- package/frontend/package.json +69 -69
- package/frontend/src/assets/icons/checkbox_checked.svg +6 -6
- package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -6
- package/frontend/src/assets/icons/checked.svg +3 -3
- package/frontend/src/assets/icons/connected.svg +3 -3
- package/frontend/src/assets/icons/error.svg +3 -3
- package/frontend/src/assets/icons/loader.svg +9 -9
- package/frontend/src/assets/icons/pencil.svg +4 -4
- package/frontend/src/assets/icons/refresh.svg +4 -4
- package/frontend/src/assets/icons/step_active.svg +3 -3
- package/frontend/src/assets/icons/step_inactive.svg +11 -11
- package/frontend/src/assets/icons/warning.svg +3 -3
- package/frontend/src/assets/logos/apple.svg +3 -3
- package/frontend/src/assets/logos/claude_code.svg +3 -3
- package/frontend/src/assets/logos/cline.svg +6 -6
- package/frontend/src/assets/logos/cursor.svg +20 -20
- package/frontend/src/assets/logos/discord.svg +8 -8
- package/frontend/src/assets/logos/facebook.svg +3 -3
- package/frontend/src/assets/logos/gemini.svg +19 -19
- package/frontend/src/assets/logos/github.svg +5 -5
- package/frontend/src/assets/logos/google.svg +13 -13
- package/frontend/src/assets/logos/grok.svg +10 -10
- package/frontend/src/assets/logos/insforge_dark.svg +15 -15
- package/frontend/src/assets/logos/insforge_light.svg +15 -15
- package/frontend/src/assets/logos/instagram.svg +1 -1
- package/frontend/src/assets/logos/linkedin.svg +3 -3
- 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 +16 -16
- package/frontend/src/assets/logos/tiktok.svg +5 -5
- package/frontend/src/assets/logos/trae.svg +3 -3
- package/frontend/src/assets/logos/windsurf.svg +10 -10
- package/frontend/src/assets/logos/x.svg +3 -3
- package/frontend/src/components/layout/AppHeader.tsx +9 -10
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +1 -0
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +6 -0
- package/frontend/src/features/auth/helpers.tsx +8 -0
- package/frontend/src/features/auth/{page → pages}/UsersPage.tsx +0 -28
- package/frontend/src/features/database/components/SQLModal.tsx +75 -0
- package/frontend/src/features/database/components/TableForm.tsx +0 -4
- package/frontend/src/features/database/hooks/useDatabase.ts +66 -0
- package/frontend/src/features/database/hooks/useTables.ts +32 -28
- package/frontend/src/features/database/index.ts +1 -0
- package/frontend/src/features/database/{page → pages}/FunctionsPage.tsx +29 -37
- package/frontend/src/features/database/{page → pages}/IndexesPage.tsx +35 -47
- package/frontend/src/features/database/{page → pages}/PoliciesPage.tsx +43 -54
- package/frontend/src/features/database/{page → pages}/TablesPage.tsx +0 -42
- package/frontend/src/features/database/{page → pages}/TriggersPage.tsx +35 -47
- package/frontend/src/features/database/services/advance.service.ts +0 -26
- package/frontend/src/features/database/services/database.service.ts +55 -0
- package/frontend/src/features/database/services/table.service.ts +0 -6
- package/frontend/src/features/functions/{page → pages}/FunctionsPage.tsx +21 -44
- package/frontend/src/features/functions/{page → pages}/SecretsPage.tsx +11 -9
- package/frontend/src/features/logs/hooks/useMcpUsage.ts +13 -66
- 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/{page → pages}/StoragePage.tsx +1 -29
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +3 -3
- package/frontend/src/features/visualizer/{page → pages}/VisualizerPage.tsx +1 -35
- package/frontend/src/lib/contexts/SocketContext.tsx +119 -75
- package/frontend/src/lib/routing/AppRoutes.tsx +35 -20
- package/frontend/src/lib/utils/cloudMessaging.ts +1 -1
- package/frontend/src/lib/utils/menuItems.ts +24 -0
- package/frontend/src/lib/utils/utils.ts +14 -1
- package/frontend/tsconfig.json +25 -25
- package/frontend/tsconfig.node.json +9 -9
- package/functions/deno.json +24 -24
- package/functions/server.ts +315 -315
- package/i18n/README.ar.md +130 -130
- package/i18n/README.de.md +130 -130
- package/i18n/README.es.md +154 -154
- package/i18n/README.fr.md +134 -134
- package/i18n/README.hi.md +129 -129
- package/i18n/README.ja.md +174 -174
- package/i18n/README.ko.md +136 -136
- package/i18n/README.pt-BR.md +131 -131
- package/i18n/README.ru.md +129 -129
- package/i18n/README.zh-CN.md +133 -133
- package/openapi/ai.yaml +715 -715
- package/openapi/auth.yaml +1244 -1244
- 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 -97
- package/shared-schemas/package.json +31 -31
- package/shared-schemas/src/ai.schema.ts +63 -59
- package/shared-schemas/src/auth-api.schema.ts +352 -339
- package/shared-schemas/src/auth.schema.ts +1 -1
- package/shared-schemas/src/database-api.schema.ts +32 -1
- package/shared-schemas/src/database.schema.ts +39 -0
- 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 +4 -0
- package/shared-schemas/src/metadata.schema.ts +9 -0
- 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 -13
- package/zeabur/template.yml +1032 -1032
- package/.cursor/rules/cursor-rules.mdc +0 -94
- package/frontend/src/features/database/hooks/useFullMetadata.ts +0 -18
- package/test-gemini.sh +0 -35
- package/test-usage-admin.sh +0 -57
- package/test-usage.sh +0 -50
- /package/frontend/src/features/ai/{page → pages}/AIPage.tsx +0 -0
- /package/frontend/src/features/auth/{page → pages}/AuthMethodsPage.tsx +0 -0
- /package/frontend/src/features/auth/{page → pages}/ConfigurationPage.tsx +0 -0
- /package/frontend/src/features/dashboard/{page → pages}/DashboardPage.tsx +0 -0
- /package/frontend/src/features/database/{page → pages}/SQLEditorPage.tsx +0 -0
- /package/frontend/src/features/database/{page → pages}/TemplatesPage.tsx +0 -0
- /package/frontend/src/features/login/{page → pages}/CloudLoginPage.tsx +0 -0
- /package/frontend/src/features/login/{page → pages}/LoginPage.tsx +0 -0
- /package/frontend/src/features/logs/{page → pages}/AuditsPage.tsx +0 -0
- /package/frontend/src/features/logs/{page → pages}/LogsPage.tsx +0 -0
- /package/frontend/src/features/logs/{page → pages}/MCPLogsPage.tsx +0 -0
|
@@ -2,17 +2,19 @@ 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
4
|
import { TokenManager } from '@/infra/security/token.manager.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { ServerEvents, ClientEvents, SocketMetadata, NotificationPayload } from '@/types/socket.js';
|
|
6
|
+
import type {
|
|
7
|
+
SubscribeChannelPayload,
|
|
8
|
+
PublishEventPayload,
|
|
8
9
|
SocketMessage,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from '@/types/socket.js';
|
|
10
|
+
SocketMessageMeta,
|
|
11
|
+
SubscribeResponse,
|
|
12
|
+
UnsubscribeChannelPayload,
|
|
13
|
+
} from '@insforge/shared-schemas';
|
|
14
14
|
import { AppError } from '@/api/middlewares/error.js';
|
|
15
15
|
import { ERROR_CODES, NEXT_ACTION } from '@/types/error-constants.js';
|
|
16
|
+
import { RealtimeAuthService } from '@/services/realtime/realtime-auth.service.js';
|
|
17
|
+
import { RealtimeMessageService } from '@/services/realtime/realtime-message.service.js';
|
|
16
18
|
|
|
17
19
|
const tokenManager = TokenManager.getInstance();
|
|
18
20
|
|
|
@@ -188,14 +190,22 @@ export class SocketManager {
|
|
|
188
190
|
* Setup handlers for client events
|
|
189
191
|
*/
|
|
190
192
|
private setupClientEventHandlers(socket: Socket): void {
|
|
191
|
-
// Handle
|
|
192
|
-
socket.on(
|
|
193
|
-
|
|
193
|
+
// Handle realtime channel subscribe with ack callback
|
|
194
|
+
socket.on(
|
|
195
|
+
ClientEvents.REALTIME_SUBSCRIBE,
|
|
196
|
+
(payload: SubscribeChannelPayload, ack: (response: SubscribeResponse) => void) => {
|
|
197
|
+
void this.handleRealtimeSubscribe(socket, payload, ack);
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Handle realtime channel unsubscribe (fire-and-forget, no ack needed)
|
|
202
|
+
socket.on(ClientEvents.REALTIME_UNSUBSCRIBE, (payload: UnsubscribeChannelPayload) => {
|
|
203
|
+
this.handleRealtimeUnsubscribe(socket, payload);
|
|
194
204
|
});
|
|
195
205
|
|
|
196
|
-
// Handle
|
|
197
|
-
socket.on(ClientEvents.
|
|
198
|
-
this.
|
|
206
|
+
// Handle realtime publish (client-initiated messages)
|
|
207
|
+
socket.on(ClientEvents.REALTIME_PUBLISH, (payload: PublishEventPayload) => {
|
|
208
|
+
void this.handleRealtimePublish(socket, payload);
|
|
199
209
|
});
|
|
200
210
|
|
|
201
211
|
// Update last activity on any event
|
|
@@ -208,70 +218,181 @@ export class SocketManager {
|
|
|
208
218
|
}
|
|
209
219
|
|
|
210
220
|
/**
|
|
211
|
-
* Handle channel
|
|
221
|
+
* Handle realtime channel subscribe request
|
|
212
222
|
*/
|
|
213
|
-
private
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
private async handleRealtimeSubscribe(
|
|
224
|
+
socket: Socket,
|
|
225
|
+
payload: SubscribeChannelPayload,
|
|
226
|
+
ack?: (response: SubscribeResponse) => void
|
|
227
|
+
): Promise<void> {
|
|
228
|
+
const authService = RealtimeAuthService.getInstance();
|
|
229
|
+
const { channel } = payload;
|
|
230
|
+
const userId = socket.data.user?.id;
|
|
231
|
+
const userRole = socket.data.user?.role;
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
// Check subscribe permission via RLS SELECT policy
|
|
235
|
+
const canSubscribe = await authService.checkSubscribePermission(channel, userId, userRole);
|
|
236
|
+
|
|
237
|
+
if (!canSubscribe) {
|
|
238
|
+
ack?.({
|
|
239
|
+
ok: false,
|
|
240
|
+
channel,
|
|
241
|
+
error: { code: 'UNAUTHORIZED', message: 'Not authorized to subscribe to this channel' },
|
|
242
|
+
});
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const roomName = `realtime:${channel}`;
|
|
247
|
+
await socket.join(roomName);
|
|
248
|
+
|
|
249
|
+
const metadata = this.socketMetadata.get(socket.id);
|
|
250
|
+
if (metadata) {
|
|
251
|
+
metadata.subscriptions.add(roomName);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
ack?.({ ok: true, channel });
|
|
255
|
+
|
|
256
|
+
logger.debug('Socket subscribed to realtime channel', {
|
|
257
|
+
socketId: socket.id,
|
|
258
|
+
channel,
|
|
259
|
+
});
|
|
260
|
+
} catch (error) {
|
|
261
|
+
logger.error('Error handling realtime subscribe', { error, channel });
|
|
262
|
+
ack?.({
|
|
263
|
+
ok: false,
|
|
264
|
+
channel,
|
|
265
|
+
error: { code: 'INTERNAL_ERROR', message: 'Failed to subscribe to channel' },
|
|
266
|
+
});
|
|
217
267
|
}
|
|
268
|
+
}
|
|
218
269
|
|
|
219
|
-
|
|
220
|
-
|
|
270
|
+
/**
|
|
271
|
+
* Handle realtime channel unsubscribe request (fire-and-forget)
|
|
272
|
+
*/
|
|
273
|
+
private handleRealtimeUnsubscribe(socket: Socket, payload: UnsubscribeChannelPayload): void {
|
|
274
|
+
const { channel } = payload;
|
|
275
|
+
const roomName = `realtime:${channel}`;
|
|
221
276
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
277
|
+
void socket.leave(roomName);
|
|
278
|
+
|
|
279
|
+
const metadata = this.socketMetadata.get(socket.id);
|
|
280
|
+
if (metadata) {
|
|
281
|
+
metadata.subscriptions.delete(roomName);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
logger.debug('Socket unsubscribed from realtime channel', { socketId: socket.id, channel });
|
|
226
285
|
}
|
|
227
286
|
|
|
228
287
|
/**
|
|
229
|
-
* Handle
|
|
288
|
+
* Handle realtime publish request (client-initiated message)
|
|
289
|
+
* Inserts message to DB - trigger handles pg_notify, broadcast, and stats update.
|
|
230
290
|
*/
|
|
231
|
-
private
|
|
291
|
+
private async handleRealtimePublish(socket: Socket, payload: PublishEventPayload): Promise<void> {
|
|
292
|
+
const { channel, event, payload: eventPayload } = payload;
|
|
293
|
+
const userId = socket.data.user?.id;
|
|
294
|
+
const userRole = socket.data.user?.role;
|
|
295
|
+
|
|
296
|
+
// Check if client has subscribed to this channel
|
|
297
|
+
const roomName = `realtime:${channel}`;
|
|
232
298
|
const metadata = this.socketMetadata.get(socket.id);
|
|
233
|
-
if (!metadata) {
|
|
299
|
+
if (!metadata?.subscriptions.has(roomName)) {
|
|
300
|
+
socket.emit(ServerEvents.REALTIME_ERROR, {
|
|
301
|
+
channel,
|
|
302
|
+
code: 'NOT_SUBSCRIBED',
|
|
303
|
+
message: 'Must subscribe to channel before publishing messages',
|
|
304
|
+
});
|
|
234
305
|
return;
|
|
235
306
|
}
|
|
236
307
|
|
|
237
|
-
|
|
238
|
-
|
|
308
|
+
try {
|
|
309
|
+
// Insert message directly - trigger will handle pg_notify and broadcasting
|
|
310
|
+
const messageService = RealtimeMessageService.getInstance();
|
|
311
|
+
const result = await messageService.insertMessage(
|
|
312
|
+
channel,
|
|
313
|
+
event,
|
|
314
|
+
eventPayload,
|
|
315
|
+
userId,
|
|
316
|
+
userRole
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
if (!result) {
|
|
320
|
+
socket.emit(ServerEvents.REALTIME_ERROR, {
|
|
321
|
+
channel,
|
|
322
|
+
code: 'UNAUTHORIZED',
|
|
323
|
+
message: 'Not authorized to publish to this channel',
|
|
324
|
+
});
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
239
327
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
328
|
+
logger.debug('Client message inserted', {
|
|
329
|
+
socketId: socket.id,
|
|
330
|
+
channel,
|
|
331
|
+
event,
|
|
332
|
+
});
|
|
333
|
+
} catch (error) {
|
|
334
|
+
logger.error('Error handling realtime publish', { error, channel });
|
|
335
|
+
socket.emit(ServerEvents.REALTIME_ERROR, {
|
|
336
|
+
channel,
|
|
337
|
+
code: 'INTERNAL_ERROR',
|
|
338
|
+
message: 'Failed to publish message',
|
|
339
|
+
});
|
|
340
|
+
}
|
|
244
341
|
}
|
|
245
342
|
|
|
246
343
|
/**
|
|
247
|
-
*
|
|
344
|
+
* Build a SocketMessage with meta and payload
|
|
248
345
|
*/
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
346
|
+
private buildSocketMessage<T extends object>(
|
|
347
|
+
payload: T,
|
|
348
|
+
meta: Omit<SocketMessageMeta, 'messageId' | 'timestamp'> & { messageId?: string }
|
|
349
|
+
): SocketMessage & T {
|
|
350
|
+
return {
|
|
351
|
+
...payload,
|
|
352
|
+
meta: {
|
|
353
|
+
...meta,
|
|
354
|
+
messageId: meta.messageId || this.generateMessageId(),
|
|
355
|
+
timestamp: new Date().toISOString(),
|
|
356
|
+
},
|
|
357
|
+
} as SocketMessage & T;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Emit message to specific socket
|
|
362
|
+
*/
|
|
363
|
+
emitToSocket<T extends object>(
|
|
364
|
+
socket: Socket,
|
|
365
|
+
event: string,
|
|
366
|
+
payload: T,
|
|
367
|
+
senderType: 'system' | 'user' = 'system',
|
|
368
|
+
senderId?: string,
|
|
369
|
+
messageId?: string
|
|
370
|
+
): void {
|
|
371
|
+
const message = this.buildSocketMessage(payload, {
|
|
372
|
+
channel: socket.id,
|
|
373
|
+
senderType,
|
|
374
|
+
senderId,
|
|
375
|
+
messageId,
|
|
376
|
+
});
|
|
256
377
|
socket.emit(event, message);
|
|
257
378
|
}
|
|
258
379
|
|
|
259
380
|
/**
|
|
260
381
|
* Broadcast to all connected clients
|
|
261
382
|
*/
|
|
262
|
-
broadcastToAll<T>(
|
|
383
|
+
broadcastToAll<T extends object>(
|
|
384
|
+
event: string,
|
|
385
|
+
payload: T,
|
|
386
|
+
senderType: 'system' | 'user' = 'system',
|
|
387
|
+
senderId?: string,
|
|
388
|
+
messageId?: string
|
|
389
|
+
): void {
|
|
263
390
|
if (!this.io) {
|
|
264
391
|
logger.warn('Socket.IO server not initialized');
|
|
265
392
|
return;
|
|
266
393
|
}
|
|
267
394
|
|
|
268
|
-
const message
|
|
269
|
-
type: event,
|
|
270
|
-
payload,
|
|
271
|
-
timestamp: Date.now(),
|
|
272
|
-
id: this.generateMessageId(),
|
|
273
|
-
};
|
|
274
|
-
|
|
395
|
+
const message = this.buildSocketMessage(payload, { senderType, senderId, messageId });
|
|
275
396
|
this.io.emit(event, message);
|
|
276
397
|
|
|
277
398
|
logger.info('Broadcasted message to all clients', {
|
|
@@ -283,25 +404,38 @@ export class SocketManager {
|
|
|
283
404
|
/**
|
|
284
405
|
* Broadcast to specific room
|
|
285
406
|
*/
|
|
286
|
-
broadcastToRoom<T>(
|
|
407
|
+
broadcastToRoom<T extends object>(
|
|
408
|
+
room: string,
|
|
409
|
+
event: string,
|
|
410
|
+
payload: T,
|
|
411
|
+
senderType: 'system' | 'user',
|
|
412
|
+
senderId?: string,
|
|
413
|
+
messageId?: string
|
|
414
|
+
): void {
|
|
287
415
|
if (!this.io) {
|
|
288
416
|
logger.warn('Socket.IO server not initialized');
|
|
289
417
|
return;
|
|
290
418
|
}
|
|
291
419
|
|
|
292
|
-
const message
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
};
|
|
298
|
-
|
|
420
|
+
const message = this.buildSocketMessage(payload, {
|
|
421
|
+
channel: room,
|
|
422
|
+
senderType,
|
|
423
|
+
senderId,
|
|
424
|
+
messageId,
|
|
425
|
+
});
|
|
299
426
|
this.io.to(room).emit(event, message);
|
|
300
427
|
|
|
301
|
-
logger.
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
428
|
+
logger.debug('Broadcasted message to room', { event, room });
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Get the number of sockets in a room
|
|
433
|
+
*/
|
|
434
|
+
getRoomSize(room: string): number {
|
|
435
|
+
if (!this.io) {
|
|
436
|
+
return 0;
|
|
437
|
+
}
|
|
438
|
+
return this.io.sockets.adapter.rooms.get(room)?.size || 0;
|
|
305
439
|
}
|
|
306
440
|
|
|
307
441
|
/**
|
|
@@ -368,11 +502,11 @@ export class SocketManager {
|
|
|
368
502
|
close(): void {
|
|
369
503
|
if (this.io) {
|
|
370
504
|
// Notify all clients about server shutdown
|
|
371
|
-
this.broadcastToAll
|
|
505
|
+
this.broadcastToAll(ServerEvents.NOTIFICATION, {
|
|
372
506
|
level: 'warning',
|
|
373
507
|
title: 'Server Shutdown',
|
|
374
508
|
message: 'Server is shutting down',
|
|
375
|
-
});
|
|
509
|
+
} as NotificationPayload);
|
|
376
510
|
|
|
377
511
|
// Close all connections
|
|
378
512
|
void this.io.close();
|
|
@@ -41,8 +41,8 @@ interface OpenRouterLimitation {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export class
|
|
45
|
-
private static instance:
|
|
44
|
+
export class OpenRouterProvider {
|
|
45
|
+
private static instance: OpenRouterProvider;
|
|
46
46
|
private cloudCredentials: CloudCredentials | undefined;
|
|
47
47
|
private openRouterClient: OpenAI | null = null;
|
|
48
48
|
private currentApiKey: string | undefined;
|
|
@@ -51,11 +51,11 @@ export class AIClientService {
|
|
|
51
51
|
|
|
52
52
|
private constructor() {}
|
|
53
53
|
|
|
54
|
-
static getInstance():
|
|
55
|
-
if (!
|
|
56
|
-
|
|
54
|
+
static getInstance(): OpenRouterProvider {
|
|
55
|
+
if (!OpenRouterProvider.instance) {
|
|
56
|
+
OpenRouterProvider.instance = new OpenRouterProvider();
|
|
57
57
|
}
|
|
58
|
-
return
|
|
58
|
+
return OpenRouterProvider.instance;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -100,9 +100,9 @@ export class AIClientService {
|
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
102
|
* Get the OpenAI client, creating or updating it as needed
|
|
103
|
-
*
|
|
103
|
+
* Used internally by sendRequest()
|
|
104
104
|
*/
|
|
105
|
-
async getClient(): Promise<OpenAI> {
|
|
105
|
+
private async getClient(): Promise<OpenAI> {
|
|
106
106
|
if (!this.openRouterClient) {
|
|
107
107
|
this.openRouterClient = this.createClient(await this.getApiKey());
|
|
108
108
|
return this.openRouterClient;
|
|
@@ -349,6 +349,9 @@ export class AIClientService {
|
|
|
349
349
|
logger.info(`Received ${error.status} insufficient credits, renewing API key...`);
|
|
350
350
|
await this.renewCloudApiKey();
|
|
351
351
|
|
|
352
|
+
// Get fresh client with renewed API key
|
|
353
|
+
const renewedClient = await this.getClient();
|
|
354
|
+
|
|
352
355
|
// Retry with exponential backoff (3 attempts)
|
|
353
356
|
const maxRetries = 3;
|
|
354
357
|
|
|
@@ -360,7 +363,7 @@ export class AIClientService {
|
|
|
360
363
|
);
|
|
361
364
|
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
362
365
|
|
|
363
|
-
const result = await request(
|
|
366
|
+
const result = await request(renewedClient);
|
|
364
367
|
logger.info('Request succeeded after API key renewal');
|
|
365
368
|
return result;
|
|
366
369
|
} catch (retryError) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EmailTemplate } from '@/types/email.js';
|
|
2
|
+
import { SendRawEmailRequest } from '@insforge/shared-schemas';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Email provider interface
|
|
@@ -25,14 +26,10 @@ export interface EmailProvider {
|
|
|
25
26
|
): Promise<void>;
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
|
-
* Send raw email
|
|
29
|
-
*
|
|
30
|
-
* @param to - Recipient email address
|
|
31
|
-
* @param subject - Email subject
|
|
32
|
-
* @param html - HTML email body
|
|
33
|
-
* @param text - Plain text email body (optional)
|
|
29
|
+
* Send custom/raw email (optional - not all providers may support this)
|
|
30
|
+
* @param options - Email options (to, subject, html, cc, bcc, from, replyTo)
|
|
34
31
|
*/
|
|
35
|
-
sendRaw?(
|
|
32
|
+
sendRaw?(options: SendRawEmailRequest): Promise<void>;
|
|
36
33
|
|
|
37
34
|
/**
|
|
38
35
|
* Check if provider supports template-based emails
|
|
@@ -5,6 +5,7 @@ import logger from '@/utils/logger.js';
|
|
|
5
5
|
import { AppError } from '@/api/middlewares/error.js';
|
|
6
6
|
import { ERROR_CODES } from '@/types/error-constants.js';
|
|
7
7
|
import { EmailTemplate } from '@/types/email.js';
|
|
8
|
+
import { SendRawEmailRequest } from '@insforge/shared-schemas';
|
|
8
9
|
import { EmailProvider } from './base.provider.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -184,4 +185,87 @@ export class CloudEmailProvider implements EmailProvider {
|
|
|
184
185
|
);
|
|
185
186
|
}
|
|
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
|
+
}
|
|
187
271
|
}
|