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
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
import { DatabaseManager } from '@/infra/database/database.manager.js';
|
|
3
|
+
import logger from '@/utils/logger.js';
|
|
4
|
+
import type { RealtimeMessage, RoleSchema } from '@insforge/shared-schemas';
|
|
5
|
+
import { RealtimeChannelService } from './realtime-channel.service.js';
|
|
6
|
+
import { RealtimeAuthService } from './realtime-auth.service.js';
|
|
7
|
+
|
|
8
|
+
export class RealtimeMessageService {
|
|
9
|
+
private static instance: RealtimeMessageService;
|
|
10
|
+
private pool: Pool | null = null;
|
|
11
|
+
|
|
12
|
+
private constructor() {}
|
|
13
|
+
|
|
14
|
+
static getInstance(): RealtimeMessageService {
|
|
15
|
+
if (!RealtimeMessageService.instance) {
|
|
16
|
+
RealtimeMessageService.instance = new RealtimeMessageService();
|
|
17
|
+
}
|
|
18
|
+
return RealtimeMessageService.instance;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private getPool(): Pool {
|
|
22
|
+
if (!this.pool) {
|
|
23
|
+
this.pool = DatabaseManager.getInstance().getPool();
|
|
24
|
+
}
|
|
25
|
+
return this.pool;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Insert a message into the channel (client-initiated send).
|
|
30
|
+
* RLS INSERT policy controls who can send to which channels.
|
|
31
|
+
* pg_notify is automatically triggered by database trigger on insert.
|
|
32
|
+
*
|
|
33
|
+
* @returns The inserted message data for broadcasting, or null if RLS denied the insert
|
|
34
|
+
*/
|
|
35
|
+
async insertMessage(
|
|
36
|
+
channelName: string,
|
|
37
|
+
eventName: string,
|
|
38
|
+
payload: Record<string, unknown>,
|
|
39
|
+
userId: string | undefined,
|
|
40
|
+
userRole: RoleSchema
|
|
41
|
+
): Promise<{
|
|
42
|
+
channelId: string;
|
|
43
|
+
channelName: string;
|
|
44
|
+
eventName: string;
|
|
45
|
+
payload: Record<string, unknown>;
|
|
46
|
+
senderId: string | null;
|
|
47
|
+
} | null> {
|
|
48
|
+
// Get channel info
|
|
49
|
+
const channelService = RealtimeChannelService.getInstance();
|
|
50
|
+
const channel = await channelService.getByName(channelName);
|
|
51
|
+
|
|
52
|
+
if (!channel) {
|
|
53
|
+
logger.debug('Channel not found for message insert', { channelName });
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const client = await this.getPool().connect();
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Begin transaction to ensure settings persist across queries
|
|
61
|
+
await client.query('BEGIN');
|
|
62
|
+
|
|
63
|
+
// Switch to specified role to enforce RLS policies
|
|
64
|
+
await client.query(`SET LOCAL ROLE ${userRole}`);
|
|
65
|
+
|
|
66
|
+
// Set user context for RLS policy evaluation
|
|
67
|
+
const authService = RealtimeAuthService.getInstance();
|
|
68
|
+
await authService.setUserContext(client, userId, channelName);
|
|
69
|
+
|
|
70
|
+
// Attempt INSERT with sender info - RLS will allow/deny based on policies
|
|
71
|
+
// No RETURNING clause needed - trigger handles pg_notify
|
|
72
|
+
await client.query(
|
|
73
|
+
`INSERT INTO realtime.messages (event_name, channel_id, channel_name, payload, sender_type, sender_id)
|
|
74
|
+
VALUES ($1, $2, $3, $4, 'user', $5)`,
|
|
75
|
+
[eventName, channel.id, channelName, JSON.stringify(payload), userId || null]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Commit transaction - insert succeeded
|
|
79
|
+
await client.query('COMMIT');
|
|
80
|
+
|
|
81
|
+
logger.debug('Client message inserted', {
|
|
82
|
+
channelName,
|
|
83
|
+
eventName,
|
|
84
|
+
userId,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
channelId: channel.id,
|
|
89
|
+
channelName,
|
|
90
|
+
eventName,
|
|
91
|
+
payload,
|
|
92
|
+
senderId: userId || null,
|
|
93
|
+
};
|
|
94
|
+
} catch (error) {
|
|
95
|
+
// Rollback transaction on error
|
|
96
|
+
await client.query('ROLLBACK').catch(() => {});
|
|
97
|
+
|
|
98
|
+
// RLS policy denied the INSERT or other error
|
|
99
|
+
logger.debug('Message insert denied or failed', { channelName, eventName, userId, error });
|
|
100
|
+
return null;
|
|
101
|
+
} finally {
|
|
102
|
+
// Reset role back to default before releasing connection
|
|
103
|
+
await client.query('RESET ROLE');
|
|
104
|
+
client.release();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get a message by ID (used by RealtimeManager after pg_notify)
|
|
110
|
+
*/
|
|
111
|
+
async getById(id: string): Promise<RealtimeMessage | null> {
|
|
112
|
+
const result = await this.getPool().query(
|
|
113
|
+
`SELECT
|
|
114
|
+
id,
|
|
115
|
+
event_name as "eventName",
|
|
116
|
+
channel_id as "channelId",
|
|
117
|
+
channel_name as "channelName",
|
|
118
|
+
payload,
|
|
119
|
+
sender_type as "senderType",
|
|
120
|
+
sender_id as "senderId",
|
|
121
|
+
ws_audience_count as "wsAudienceCount",
|
|
122
|
+
wh_audience_count as "whAudienceCount",
|
|
123
|
+
wh_delivered_count as "whDeliveredCount",
|
|
124
|
+
created_at as "createdAt"
|
|
125
|
+
FROM realtime.messages
|
|
126
|
+
WHERE id = $1`,
|
|
127
|
+
[id]
|
|
128
|
+
);
|
|
129
|
+
return result.rows[0] || null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async list(
|
|
133
|
+
options: {
|
|
134
|
+
channelId?: string;
|
|
135
|
+
eventName?: string;
|
|
136
|
+
limit?: number;
|
|
137
|
+
offset?: number;
|
|
138
|
+
} = {}
|
|
139
|
+
): Promise<RealtimeMessage[]> {
|
|
140
|
+
const { channelId, eventName, limit = 100, offset = 0 } = options;
|
|
141
|
+
|
|
142
|
+
let query = `
|
|
143
|
+
SELECT
|
|
144
|
+
id,
|
|
145
|
+
event_name as "eventName",
|
|
146
|
+
channel_id as "channelId",
|
|
147
|
+
channel_name as "channelName",
|
|
148
|
+
payload,
|
|
149
|
+
sender_type as "senderType",
|
|
150
|
+
sender_id as "senderId",
|
|
151
|
+
ws_audience_count as "wsAudienceCount",
|
|
152
|
+
wh_audience_count as "whAudienceCount",
|
|
153
|
+
wh_delivered_count as "whDeliveredCount",
|
|
154
|
+
created_at as "createdAt"
|
|
155
|
+
FROM realtime.messages
|
|
156
|
+
WHERE 1=1
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
const params: (string | number)[] = [];
|
|
160
|
+
let paramIndex = 1;
|
|
161
|
+
|
|
162
|
+
if (channelId) {
|
|
163
|
+
query += ` AND channel_id = $${paramIndex++}`;
|
|
164
|
+
params.push(channelId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (eventName) {
|
|
168
|
+
query += ` AND event_name = $${paramIndex++}`;
|
|
169
|
+
params.push(eventName);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
query += ` ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
|
|
173
|
+
params.push(limit, offset);
|
|
174
|
+
|
|
175
|
+
const result = await this.getPool().query(query, params);
|
|
176
|
+
return result.rows;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Update message record with delivery statistics
|
|
181
|
+
*/
|
|
182
|
+
async updateDeliveryStats(
|
|
183
|
+
messageId: string,
|
|
184
|
+
stats: {
|
|
185
|
+
wsAudienceCount: number;
|
|
186
|
+
whAudienceCount: number;
|
|
187
|
+
whDeliveredCount: number;
|
|
188
|
+
}
|
|
189
|
+
): Promise<void> {
|
|
190
|
+
await this.getPool().query(
|
|
191
|
+
`UPDATE realtime.messages
|
|
192
|
+
SET
|
|
193
|
+
ws_audience_count = $2,
|
|
194
|
+
wh_audience_count = $3,
|
|
195
|
+
wh_delivered_count = $4
|
|
196
|
+
WHERE id = $1`,
|
|
197
|
+
[messageId, stats.wsAudienceCount, stats.whAudienceCount, stats.whDeliveredCount]
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async getStats(
|
|
202
|
+
options: {
|
|
203
|
+
channelId?: string;
|
|
204
|
+
since?: Date;
|
|
205
|
+
} = {}
|
|
206
|
+
): Promise<{
|
|
207
|
+
totalMessages: number;
|
|
208
|
+
whDeliveryRate: number;
|
|
209
|
+
topEvents: { eventName: string; count: number }[];
|
|
210
|
+
}> {
|
|
211
|
+
const { channelId, since } = options;
|
|
212
|
+
|
|
213
|
+
let whereClause = '1=1';
|
|
214
|
+
const params: (string | Date)[] = [];
|
|
215
|
+
let paramIndex = 1;
|
|
216
|
+
|
|
217
|
+
if (channelId) {
|
|
218
|
+
whereClause += ` AND channel_id = $${paramIndex++}`;
|
|
219
|
+
params.push(channelId);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (since) {
|
|
223
|
+
whereClause += ` AND created_at >= $${paramIndex++}`;
|
|
224
|
+
params.push(since);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const statsResult = await this.getPool().query(
|
|
228
|
+
`SELECT
|
|
229
|
+
COUNT(*) as total_messages,
|
|
230
|
+
SUM(wh_audience_count) as wh_audience_total,
|
|
231
|
+
SUM(wh_delivered_count) as wh_delivered_total
|
|
232
|
+
FROM realtime.messages
|
|
233
|
+
WHERE ${whereClause}`,
|
|
234
|
+
params
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const topEventsResult = await this.getPool().query(
|
|
238
|
+
`SELECT event_name, COUNT(*) as count
|
|
239
|
+
FROM realtime.messages
|
|
240
|
+
WHERE ${whereClause}
|
|
241
|
+
GROUP BY event_name
|
|
242
|
+
ORDER BY count DESC
|
|
243
|
+
LIMIT 10`,
|
|
244
|
+
params
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const stats = statsResult.rows[0];
|
|
248
|
+
const whAudienceTotal = parseInt(stats.wh_audience_total) || 0;
|
|
249
|
+
const whDeliveredTotal = parseInt(stats.wh_delivered_total) || 0;
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
totalMessages: parseInt(stats.total_messages) || 0,
|
|
253
|
+
whDeliveryRate: whAudienceTotal > 0 ? whDeliveredTotal / whAudienceTotal : 0,
|
|
254
|
+
topEvents: topEventsResult.rows.map((row) => ({
|
|
255
|
+
eventName: row.event_name,
|
|
256
|
+
count: parseInt(row.count),
|
|
257
|
+
})),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -135,6 +135,16 @@ export interface XUserInfo {
|
|
|
135
135
|
created_at?: string;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
export interface AppleUserInfo {
|
|
139
|
+
sub: string;
|
|
140
|
+
email: string;
|
|
141
|
+
email_verified?: boolean;
|
|
142
|
+
is_private_email?: boolean;
|
|
143
|
+
name?: string;
|
|
144
|
+
given_name?: string;
|
|
145
|
+
family_name?: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
138
148
|
// Generic OAuth user data returned by provider services
|
|
139
149
|
export interface OAuthUserData {
|
|
140
150
|
provider: string;
|
|
@@ -150,5 +160,6 @@ export interface OAuthUserData {
|
|
|
150
160
|
| FacebookUserInfo
|
|
151
161
|
| MicrosoftUserInfo
|
|
152
162
|
| XUserInfo
|
|
163
|
+
| AppleUserInfo
|
|
153
164
|
| Record<string, unknown>;
|
|
154
165
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realtime feature types - Backend-only types
|
|
3
|
+
*
|
|
4
|
+
* Shared types should be imported directly from @insforge/shared-schemas.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Backend-Only Types (Internal Use)
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Delivery statistics after message processing
|
|
13
|
+
*/
|
|
14
|
+
export interface DeliveryResult {
|
|
15
|
+
wsAudienceCount: number;
|
|
16
|
+
whAudienceCount: number;
|
|
17
|
+
whDeliveredCount: number;
|
|
18
|
+
}
|
|
@@ -10,24 +10,18 @@ export enum ServerEvents {
|
|
|
10
10
|
NOTIFICATION = 'notification',
|
|
11
11
|
DATA_UPDATE = 'data:update',
|
|
12
12
|
MCP_CONNECTED = 'mcp:connected',
|
|
13
|
+
// Realtime events
|
|
14
|
+
REALTIME_ERROR = 'realtime:error',
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Client-to-Server events
|
|
17
19
|
*/
|
|
18
20
|
export enum ClientEvents {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Generic message interface
|
|
25
|
-
*/
|
|
26
|
-
export interface SocketMessage<T = unknown> {
|
|
27
|
-
type: string;
|
|
28
|
-
payload?: T;
|
|
29
|
-
timestamp: number;
|
|
30
|
-
id?: string;
|
|
21
|
+
// Realtime events
|
|
22
|
+
REALTIME_SUBSCRIBE = 'realtime:subscribe',
|
|
23
|
+
REALTIME_UNSUBSCRIBE = 'realtime:unsubscribe',
|
|
24
|
+
REALTIME_PUBLISH = 'realtime:publish',
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
/**
|
|
@@ -43,27 +37,9 @@ export interface NotificationPayload {
|
|
|
43
37
|
export enum DataUpdateResourceType {
|
|
44
38
|
DATABASE = 'database',
|
|
45
39
|
USERS = 'users',
|
|
46
|
-
RECORDS = 'records',
|
|
47
40
|
BUCKETS = 'buckets',
|
|
48
41
|
FUNCTIONS = 'functions',
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
export interface DataUpdatePayload {
|
|
52
|
-
resource: DataUpdateResourceType;
|
|
53
|
-
action: 'created' | 'updated' | 'deleted';
|
|
54
|
-
data: unknown;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Client event payloads
|
|
59
|
-
*/
|
|
60
|
-
export interface SubscribePayload {
|
|
61
|
-
channel: string;
|
|
62
|
-
filters?: Record<string, unknown>;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface UnsubscribePayload {
|
|
66
|
-
channel: string;
|
|
42
|
+
REALTIME = 'realtime',
|
|
67
43
|
}
|
|
68
44
|
|
|
69
45
|
/**
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Response } from 'express';
|
|
2
|
+
import { isProduction } from './environment';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cookie names
|
|
6
|
+
*/
|
|
7
|
+
export const REFRESH_TOKEN_COOKIE_NAME = 'insforge_refresh_token';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Set an auth cookie on response
|
|
11
|
+
* @param name - Cookie name
|
|
12
|
+
* @param value - Cookie value
|
|
13
|
+
*/
|
|
14
|
+
export function setAuthCookie(res: Response, name: string, value: string): void {
|
|
15
|
+
res.cookie(name, value, {
|
|
16
|
+
httpOnly: true,
|
|
17
|
+
secure: isProduction(),
|
|
18
|
+
sameSite: 'none',
|
|
19
|
+
path: '/api/auth',
|
|
20
|
+
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Clear an auth cookie on response
|
|
26
|
+
* IMPORTANT: Must use the same options (especially path) as when setting the cookie
|
|
27
|
+
*/
|
|
28
|
+
export function clearAuthCookie(res: Response, name: string): void {
|
|
29
|
+
res.clearCookie(name, {
|
|
30
|
+
httpOnly: true,
|
|
31
|
+
secure: isProduction(),
|
|
32
|
+
sameSite: 'none',
|
|
33
|
+
path: '/api/auth',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
2
|
+
import logger from '@/utils/logger.js';
|
|
3
|
+
|
|
4
|
+
// TODO: make these configurable in env variables in cloud backend
|
|
5
|
+
const CONFIG_BUCKET = process.env.AWS_CONFIG_BUCKET || 'insforge-config';
|
|
6
|
+
const CONFIG_REGION = process.env.AWS_CONFIG_REGION || 'us-east-2';
|
|
7
|
+
|
|
8
|
+
let s3Client: S3Client | null = null;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get or create S3 client for config loading
|
|
12
|
+
*/
|
|
13
|
+
function getS3Client(): S3Client {
|
|
14
|
+
if (s3Client) {
|
|
15
|
+
return s3Client;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const s3Config: {
|
|
19
|
+
region: string;
|
|
20
|
+
credentials?: { accessKeyId: string; secretAccessKey: string };
|
|
21
|
+
} = {
|
|
22
|
+
region: CONFIG_REGION,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Use explicit credentials if provided, otherwise IAM role
|
|
26
|
+
if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
|
|
27
|
+
s3Config.credentials = {
|
|
28
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
29
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
s3Client = new S3Client(s3Config);
|
|
34
|
+
return s3Client;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fetches a JSON config file from the S3 config bucket
|
|
39
|
+
* @param key - The S3 object key (e.g., 'default-ai-models.json')
|
|
40
|
+
* @returns Parsed JSON content or null if fetch fails
|
|
41
|
+
*/
|
|
42
|
+
export async function fetchS3Config<T>(key: string): Promise<T | null> {
|
|
43
|
+
try {
|
|
44
|
+
const command = new GetObjectCommand({
|
|
45
|
+
Bucket: CONFIG_BUCKET,
|
|
46
|
+
Key: key,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const response = await getS3Client().send(command);
|
|
50
|
+
const body = await response.Body?.transformToString();
|
|
51
|
+
|
|
52
|
+
if (!body) {
|
|
53
|
+
logger.warn(`Empty config file from S3: ${key}`);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return JSON.parse(body) as T;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
logger.warn(`Failed to fetch config from S3: ${key}`, {
|
|
60
|
+
error: error instanceof Error ? error.message : String(error),
|
|
61
|
+
});
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|