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.
Files changed (335) hide show
  1. package/.claude-plugin/marketplace.json +20 -20
  2. package/.dockerignore +60 -60
  3. package/.env.example +83 -77
  4. package/.github/ISSUE_TEMPLATE/bug_report.yml +36 -36
  5. package/.github/ISSUE_TEMPLATE/config.yml +11 -11
  6. package/.github/ISSUE_TEMPLATE/feature_request.yml +26 -26
  7. package/.github/PULL_REQUEST_TEMPLATE.md +7 -7
  8. package/.github/copilot-instructions.md +146 -146
  9. package/.github/workflows/build-image.yml +65 -65
  10. package/.github/workflows/ci-premerge-check.yml +23 -23
  11. package/.github/workflows/e2e.yml +63 -63
  12. package/.github/workflows/lint-and-format.yml +32 -32
  13. package/.prettierignore +64 -64
  14. package/CHANGELOG.md +44 -44
  15. package/CLAUDE_PLUGIN.md +104 -104
  16. package/CODE_OF_CONDUCT.md +128 -128
  17. package/CONTRIBUTING.md +125 -125
  18. package/Dockerfile +30 -30
  19. package/GITHUB_OAUTH_SETUP.md +49 -49
  20. package/GOOGLE_OAUTH_SETUP.md +148 -148
  21. package/LICENSE +201 -201
  22. package/README.md +182 -182
  23. package/assets/Dark.svg +23 -23
  24. package/auth/package.json +28 -28
  25. package/auth/src/lib/broadcastService.ts +117 -115
  26. package/auth/src/pages/SignInPage.tsx +60 -57
  27. package/auth/src/pages/SignUpPage.tsx +60 -57
  28. package/auth/tsconfig.json +32 -32
  29. package/auth/tsconfig.node.json +11 -11
  30. package/backend/package.json +78 -75
  31. package/backend/src/api/routes/ai/index.routes.ts +3 -3
  32. package/backend/src/api/routes/auth/index.routes.ts +667 -570
  33. package/backend/src/api/routes/auth/oauth.routes.ts +473 -448
  34. package/backend/src/api/routes/database/advance.routes.ts +37 -16
  35. package/backend/src/api/routes/database/index.routes.ts +78 -1
  36. package/backend/src/api/routes/database/records.routes.ts +10 -10
  37. package/backend/src/api/routes/database/tables.routes.ts +0 -14
  38. package/backend/src/api/routes/docs/index.routes.ts +75 -76
  39. package/backend/src/api/routes/email/index.routes.ts +35 -0
  40. package/backend/src/api/routes/functions/index.routes.ts +18 -12
  41. package/backend/src/api/routes/metadata/index.routes.ts +12 -0
  42. package/backend/src/api/routes/realtime/channels.routes.ts +81 -0
  43. package/backend/src/api/routes/realtime/index.routes.ts +12 -0
  44. package/backend/src/api/routes/realtime/messages.routes.ts +48 -0
  45. package/backend/src/api/routes/realtime/permissions.routes.ts +19 -0
  46. package/backend/src/api/routes/storage/index.routes.ts +18 -12
  47. package/backend/src/api/routes/usage/index.routes.ts +6 -4
  48. package/backend/src/infra/database/database.manager.ts +14 -1
  49. package/backend/src/infra/database/migrations/000_create-base-tables.sql +141 -141
  50. package/backend/src/infra/database/migrations/001_create-helper-functions.sql +40 -40
  51. package/backend/src/infra/database/migrations/002_rename-auth-tables.sql +29 -29
  52. package/backend/src/infra/database/migrations/003_create-users-table.sql +55 -55
  53. package/backend/src/infra/database/migrations/004_add-reload-postgrest-func.sql +23 -23
  54. package/backend/src/infra/database/migrations/005_enable-project-admin-modify-users.sql +29 -29
  55. package/backend/src/infra/database/migrations/006_modify-ai-usage-table.sql +24 -24
  56. package/backend/src/infra/database/migrations/007_drop-metadata-table.sql +1 -1
  57. package/backend/src/infra/database/migrations/008_add-system-tables.sql +76 -76
  58. package/backend/src/infra/database/migrations/009_add-function-secrets.sql +23 -23
  59. package/backend/src/infra/database/migrations/010_modify-ai-config-modalities.sql +93 -93
  60. package/backend/src/infra/database/migrations/011_refactor-secrets-table.sql +15 -15
  61. package/backend/src/infra/database/migrations/012_add-storage-uploaded-by.sql +7 -7
  62. package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -44
  63. package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +7 -7
  64. package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +59 -59
  65. package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -24
  66. package/backend/src/infra/database/migrations/017_create-realtime-schema.sql +233 -0
  67. package/backend/src/infra/realtime/realtime.manager.ts +246 -0
  68. package/backend/src/infra/realtime/webhook-sender.ts +82 -0
  69. package/backend/src/infra/security/token.manager.ts +219 -125
  70. package/backend/src/infra/socket/socket.manager.ts +198 -64
  71. package/backend/src/providers/ai/openrouter.provider.ts +12 -9
  72. package/backend/src/providers/email/base.provider.ts +4 -7
  73. package/backend/src/providers/email/cloud.provider.ts +84 -0
  74. package/backend/src/providers/oauth/apple.provider.ts +266 -0
  75. package/backend/src/providers/oauth/index.ts +1 -0
  76. package/backend/src/server.ts +317 -284
  77. package/backend/src/services/ai/ai-model.service.ts +5 -5
  78. package/backend/src/services/ai/chat-completion.service.ts +4 -4
  79. package/backend/src/services/ai/image-generation.service.ts +3 -3
  80. package/backend/src/services/auth/auth.service.ts +14 -0
  81. package/backend/src/services/database/database-table.service.ts +0 -9
  82. package/backend/src/services/database/database.service.ts +127 -0
  83. package/backend/src/services/email/email.service.ts +5 -7
  84. package/backend/src/services/realtime/index.ts +3 -0
  85. package/backend/src/services/realtime/realtime-auth.service.ts +104 -0
  86. package/backend/src/services/realtime/realtime-channel.service.ts +237 -0
  87. package/backend/src/services/realtime/realtime-message.service.ts +260 -0
  88. package/backend/src/types/auth.ts +11 -0
  89. package/backend/src/types/realtime.ts +18 -0
  90. package/backend/src/types/socket.ts +7 -31
  91. package/backend/src/utils/cookies.ts +35 -0
  92. package/backend/src/utils/s3-config-loader.ts +64 -0
  93. package/backend/src/utils/seed.ts +301 -298
  94. package/backend/src/utils/sql-parser.ts +90 -0
  95. package/backend/tests/README.md +133 -133
  96. package/backend/tests/cleanup-all-test-data.sh +230 -230
  97. package/backend/tests/cloud/test-s3-multitenant.sh +131 -131
  98. package/backend/tests/local/comprehensive-curl-tests.sh +155 -155
  99. package/backend/tests/local/test-ai-config.sh +129 -129
  100. package/backend/tests/local/test-ai-usage.sh +80 -80
  101. package/backend/tests/local/test-auth-router.sh +143 -143
  102. package/backend/tests/local/test-database-router.sh +222 -222
  103. package/backend/tests/local/test-e2e.sh +240 -240
  104. package/backend/tests/local/test-fk-errors.sh +96 -96
  105. package/backend/tests/local/test-functions.sh +123 -123
  106. package/backend/tests/local/test-id-field.sh +200 -200
  107. package/backend/tests/local/test-logs.sh +132 -132
  108. package/backend/tests/local/test-public-bucket.sh +264 -264
  109. package/backend/tests/local/test-secrets.sh +249 -249
  110. package/backend/tests/local/test-serverless-functions.sh.disabled +325 -325
  111. package/backend/tests/local/test-traditional-rest.sh +208 -208
  112. package/backend/tests/manual/README.md +50 -50
  113. package/backend/tests/manual/create-large-table-simple.sql +10 -10
  114. package/backend/tests/manual/seed-large-table.sql +100 -100
  115. package/backend/tests/manual/setup-large-table-extras.sql +33 -33
  116. package/backend/tests/manual/test-bulk-upsert.sh +409 -409
  117. package/backend/tests/manual/test-database-advance.sh +296 -296
  118. package/backend/tests/manual/test-postgrest-stability.sh +191 -191
  119. package/backend/tests/manual/test-rawsql-export-import.sh +411 -411
  120. package/backend/tests/manual/test-rawsql-modes.sh +244 -244
  121. package/backend/tests/manual/test-universal-storage.sh +263 -263
  122. package/backend/tests/manual/test-users.sql +17 -17
  123. package/backend/tests/run-all-tests.sh +139 -139
  124. package/backend/tests/setup.ts +0 -0
  125. package/backend/tests/test-config.sh +338 -338
  126. package/backend/tests/unit/analyze-query.test.ts +697 -0
  127. package/backend/tsconfig.json +22 -22
  128. package/claude-plugin/.claude-plugin/plugin.json +24 -24
  129. package/claude-plugin/README.md +133 -133
  130. package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +270 -270
  131. package/docker-compose.prod.yml +204 -200
  132. package/docker-compose.yml +232 -228
  133. package/docker-init/db/db-init.sql +97 -97
  134. package/docker-init/db/jwt.sql +5 -5
  135. package/docker-init/db/postgresql.conf +16 -16
  136. package/docker-init/logs/vector.yml +236 -236
  137. package/docs/README.md +44 -44
  138. package/docs/agent-docs/real-time.md +269 -0
  139. package/docs/changelog.mdx +119 -67
  140. package/docs/core-concepts/ai/architecture.mdx +372 -372
  141. package/docs/core-concepts/ai/sdk.mdx +213 -213
  142. package/docs/core-concepts/authentication/architecture.mdx +278 -278
  143. package/docs/core-concepts/authentication/sdk.mdx +414 -414
  144. package/docs/core-concepts/authentication/ui-components/customization.mdx +529 -529
  145. package/docs/core-concepts/authentication/ui-components/nextjs.mdx +221 -221
  146. package/docs/core-concepts/authentication/ui-components/react-router.mdx +184 -184
  147. package/docs/core-concepts/authentication/ui-components/react.mdx +129 -129
  148. package/docs/core-concepts/database/architecture.mdx +255 -255
  149. package/docs/core-concepts/database/sdk.mdx +382 -382
  150. package/docs/core-concepts/email/architecture.mdx +101 -0
  151. package/docs/core-concepts/email/sdk.mdx +53 -0
  152. package/docs/core-concepts/functions/architecture.mdx +105 -105
  153. package/docs/core-concepts/functions/sdk.mdx +184 -184
  154. package/docs/core-concepts/realtime/architecture.mdx +446 -0
  155. package/docs/core-concepts/realtime/sdk.mdx +409 -0
  156. package/docs/core-concepts/storage/architecture.mdx +243 -243
  157. package/docs/core-concepts/storage/sdk.mdx +253 -253
  158. package/docs/deployment/README.md +94 -94
  159. package/docs/deployment/deploy-to-aws-ec2.md +564 -564
  160. package/docs/deployment/deploy-to-azure-virtual-machines.md +312 -312
  161. package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -613
  162. package/docs/deployment/deploy-to-render.md +441 -441
  163. package/docs/deprecated/insforge-auth-api.md +214 -214
  164. package/docs/deprecated/insforge-auth-sdk.md +99 -99
  165. package/docs/deprecated/insforge-db-api.md +358 -358
  166. package/docs/deprecated/insforge-db-sdk.md +139 -139
  167. package/docs/deprecated/insforge-debug-sdk.md +156 -156
  168. package/docs/deprecated/insforge-debug.md +64 -64
  169. package/docs/deprecated/insforge-instructions.md +123 -123
  170. package/docs/deprecated/insforge-project.md +117 -117
  171. package/docs/deprecated/insforge-storage-api.md +278 -278
  172. package/docs/deprecated/insforge-storage-sdk.md +158 -158
  173. package/docs/docs.json +232 -210
  174. package/docs/examples/framework-guides/nextjs.mdx +131 -131
  175. package/docs/examples/framework-guides/nuxt.mdx +165 -165
  176. package/docs/examples/framework-guides/react.mdx +165 -165
  177. package/docs/examples/framework-guides/svelte.mdx +153 -153
  178. package/docs/examples/framework-guides/vue.mdx +159 -159
  179. package/docs/examples/overview.mdx +67 -67
  180. package/docs/favicon.svg +19 -19
  181. package/docs/images/changelog/dec-2025/ai-integration.png +0 -0
  182. package/docs/images/changelog/dec-2025/ai-models.webp +0 -0
  183. package/docs/images/changelog/dec-2025/alipay-payment.webp +0 -0
  184. package/docs/images/changelog/dec-2025/apple-login.jpg +0 -0
  185. package/docs/images/changelog/dec-2025/mcp-installer.png +0 -0
  186. package/docs/images/changelog/dec-2025/realtime-module.jpg +0 -0
  187. package/docs/images/icons/ai.svg +4 -4
  188. package/docs/images/logos/nextjs.svg +4 -4
  189. package/docs/images/logos/nuxt.svg +4 -4
  190. package/docs/images/logos/react.svg +5 -5
  191. package/docs/images/logos/svelte.svg +4 -4
  192. package/docs/images/logos/vue.svg +5 -5
  193. package/docs/insforge-instructions-sdk.md +89 -88
  194. package/docs/introduction.mdx +45 -45
  195. package/docs/logo/dark.svg +22 -22
  196. package/docs/logo/light.svg +20 -20
  197. package/docs/partnership.mdx +651 -646
  198. package/docs/quickstart.mdx +82 -82
  199. package/docs/showcase.mdx +52 -52
  200. package/docs/snippets/sdk-installation.mdx +21 -21
  201. package/docs/snippets/service-icons.mdx +27 -27
  202. package/examples/oauth/frontend-oauth-example.html +250 -250
  203. package/examples/response-examples.md +443 -443
  204. package/frontend/components.json +17 -17
  205. package/frontend/package.json +69 -69
  206. package/frontend/src/assets/icons/checkbox_checked.svg +6 -6
  207. package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -6
  208. package/frontend/src/assets/icons/checked.svg +3 -3
  209. package/frontend/src/assets/icons/connected.svg +3 -3
  210. package/frontend/src/assets/icons/error.svg +3 -3
  211. package/frontend/src/assets/icons/loader.svg +9 -9
  212. package/frontend/src/assets/icons/pencil.svg +4 -4
  213. package/frontend/src/assets/icons/refresh.svg +4 -4
  214. package/frontend/src/assets/icons/step_active.svg +3 -3
  215. package/frontend/src/assets/icons/step_inactive.svg +11 -11
  216. package/frontend/src/assets/icons/warning.svg +3 -3
  217. package/frontend/src/assets/logos/apple.svg +3 -3
  218. package/frontend/src/assets/logos/claude_code.svg +3 -3
  219. package/frontend/src/assets/logos/cline.svg +6 -6
  220. package/frontend/src/assets/logos/cursor.svg +20 -20
  221. package/frontend/src/assets/logos/discord.svg +8 -8
  222. package/frontend/src/assets/logos/facebook.svg +3 -3
  223. package/frontend/src/assets/logos/gemini.svg +19 -19
  224. package/frontend/src/assets/logos/github.svg +5 -5
  225. package/frontend/src/assets/logos/google.svg +13 -13
  226. package/frontend/src/assets/logos/grok.svg +10 -10
  227. package/frontend/src/assets/logos/insforge_dark.svg +15 -15
  228. package/frontend/src/assets/logos/insforge_light.svg +15 -15
  229. package/frontend/src/assets/logos/instagram.svg +1 -1
  230. package/frontend/src/assets/logos/linkedin.svg +3 -3
  231. package/frontend/src/assets/logos/openai.svg +10 -10
  232. package/frontend/src/assets/logos/roo_code.svg +9 -9
  233. package/frontend/src/assets/logos/spotify.svg +16 -16
  234. package/frontend/src/assets/logos/tiktok.svg +5 -5
  235. package/frontend/src/assets/logos/trae.svg +3 -3
  236. package/frontend/src/assets/logos/windsurf.svg +10 -10
  237. package/frontend/src/assets/logos/x.svg +3 -3
  238. package/frontend/src/components/layout/AppHeader.tsx +9 -10
  239. package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +1 -0
  240. package/frontend/src/features/auth/components/UsersDataGrid.tsx +6 -0
  241. package/frontend/src/features/auth/helpers.tsx +8 -0
  242. package/frontend/src/features/auth/{page → pages}/UsersPage.tsx +0 -28
  243. package/frontend/src/features/database/components/SQLModal.tsx +75 -0
  244. package/frontend/src/features/database/components/TableForm.tsx +0 -4
  245. package/frontend/src/features/database/hooks/useDatabase.ts +66 -0
  246. package/frontend/src/features/database/hooks/useTables.ts +32 -28
  247. package/frontend/src/features/database/index.ts +1 -0
  248. package/frontend/src/features/database/{page → pages}/FunctionsPage.tsx +29 -37
  249. package/frontend/src/features/database/{page → pages}/IndexesPage.tsx +35 -47
  250. package/frontend/src/features/database/{page → pages}/PoliciesPage.tsx +43 -54
  251. package/frontend/src/features/database/{page → pages}/TablesPage.tsx +0 -42
  252. package/frontend/src/features/database/{page → pages}/TriggersPage.tsx +35 -47
  253. package/frontend/src/features/database/services/advance.service.ts +0 -26
  254. package/frontend/src/features/database/services/database.service.ts +55 -0
  255. package/frontend/src/features/database/services/table.service.ts +0 -6
  256. package/frontend/src/features/functions/{page → pages}/FunctionsPage.tsx +21 -44
  257. package/frontend/src/features/functions/{page → pages}/SecretsPage.tsx +11 -9
  258. package/frontend/src/features/logs/hooks/useMcpUsage.ts +13 -66
  259. package/frontend/src/features/realtime/components/ChannelRow.tsx +83 -0
  260. package/frontend/src/features/realtime/components/EditChannelModal.tsx +246 -0
  261. package/frontend/src/features/realtime/components/MessageRow.tsx +85 -0
  262. package/frontend/src/features/realtime/components/RealtimeEmptyState.tsx +30 -0
  263. package/frontend/src/features/realtime/hooks/useRealtime.ts +218 -0
  264. package/frontend/src/features/realtime/index.ts +11 -0
  265. package/frontend/src/features/realtime/pages/RealtimeChannelsPage.tsx +172 -0
  266. package/frontend/src/features/realtime/pages/RealtimeMessagesPage.tsx +211 -0
  267. package/frontend/src/features/realtime/pages/RealtimePermissionsPage.tsx +191 -0
  268. package/frontend/src/features/realtime/services/realtime.service.ts +107 -0
  269. package/frontend/src/features/storage/{page → pages}/StoragePage.tsx +1 -29
  270. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +3 -3
  271. package/frontend/src/features/visualizer/{page → pages}/VisualizerPage.tsx +1 -35
  272. package/frontend/src/lib/contexts/SocketContext.tsx +119 -75
  273. package/frontend/src/lib/routing/AppRoutes.tsx +35 -20
  274. package/frontend/src/lib/utils/cloudMessaging.ts +1 -1
  275. package/frontend/src/lib/utils/menuItems.ts +24 -0
  276. package/frontend/src/lib/utils/utils.ts +14 -1
  277. package/frontend/tsconfig.json +25 -25
  278. package/frontend/tsconfig.node.json +9 -9
  279. package/functions/deno.json +24 -24
  280. package/functions/server.ts +315 -315
  281. package/i18n/README.ar.md +130 -130
  282. package/i18n/README.de.md +130 -130
  283. package/i18n/README.es.md +154 -154
  284. package/i18n/README.fr.md +134 -134
  285. package/i18n/README.hi.md +129 -129
  286. package/i18n/README.ja.md +174 -174
  287. package/i18n/README.ko.md +136 -136
  288. package/i18n/README.pt-BR.md +131 -131
  289. package/i18n/README.ru.md +129 -129
  290. package/i18n/README.zh-CN.md +133 -133
  291. package/openapi/ai.yaml +715 -715
  292. package/openapi/auth.yaml +1244 -1244
  293. package/openapi/email.yaml +158 -0
  294. package/openapi/functions.yaml +475 -475
  295. package/openapi/health.yaml +29 -29
  296. package/openapi/logs.yaml +223 -223
  297. package/openapi/metadata.yaml +177 -177
  298. package/openapi/realtime.yaml +699 -0
  299. package/openapi/records.yaml +381 -381
  300. package/openapi/secrets.yaml +370 -370
  301. package/openapi/storage.yaml +875 -875
  302. package/openapi/tables.yaml +463 -463
  303. package/package.json +97 -97
  304. package/shared-schemas/package.json +31 -31
  305. package/shared-schemas/src/ai.schema.ts +63 -59
  306. package/shared-schemas/src/auth-api.schema.ts +352 -339
  307. package/shared-schemas/src/auth.schema.ts +1 -1
  308. package/shared-schemas/src/database-api.schema.ts +32 -1
  309. package/shared-schemas/src/database.schema.ts +39 -0
  310. package/shared-schemas/src/docs.schema.ts +26 -0
  311. package/shared-schemas/src/email-api.schema.ts +30 -0
  312. package/shared-schemas/src/index.ts +4 -0
  313. package/shared-schemas/src/metadata.schema.ts +9 -0
  314. package/shared-schemas/src/realtime-api.schema.ts +111 -0
  315. package/shared-schemas/src/realtime.schema.ts +143 -0
  316. package/shared-schemas/tsconfig.json +21 -21
  317. package/tsconfig.json +7 -7
  318. package/zeabur/README.md +13 -13
  319. package/zeabur/template.yml +1032 -1032
  320. package/.cursor/rules/cursor-rules.mdc +0 -94
  321. package/frontend/src/features/database/hooks/useFullMetadata.ts +0 -18
  322. package/test-gemini.sh +0 -35
  323. package/test-usage-admin.sh +0 -57
  324. package/test-usage.sh +0 -50
  325. /package/frontend/src/features/ai/{page → pages}/AIPage.tsx +0 -0
  326. /package/frontend/src/features/auth/{page → pages}/AuthMethodsPage.tsx +0 -0
  327. /package/frontend/src/features/auth/{page → pages}/ConfigurationPage.tsx +0 -0
  328. /package/frontend/src/features/dashboard/{page → pages}/DashboardPage.tsx +0 -0
  329. /package/frontend/src/features/database/{page → pages}/SQLEditorPage.tsx +0 -0
  330. /package/frontend/src/features/database/{page → pages}/TemplatesPage.tsx +0 -0
  331. /package/frontend/src/features/login/{page → pages}/CloudLoginPage.tsx +0 -0
  332. /package/frontend/src/features/login/{page → pages}/LoginPage.tsx +0 -0
  333. /package/frontend/src/features/logs/{page → pages}/AuditsPage.tsx +0 -0
  334. /package/frontend/src/features/logs/{page → pages}/LogsPage.tsx +0 -0
  335. /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
- SUBSCRIBE = 'subscribe',
20
- UNSUBSCRIBE = 'unsubscribe',
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
+ }