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
@@ -1,7 +1,7 @@
1
1
  import OpenAI from 'openai';
2
2
  import { AIUsageService } from './ai-usage.service.js';
3
3
  import { AIConfigService } from './ai-config.service.js';
4
- import { AIClientService } from '@/providers/ai/openrouter.provider.js';
4
+ import { OpenRouterProvider } from '@/providers/ai/openrouter.provider.js';
5
5
  import type {
6
6
  AIConfigurationSchema,
7
7
  ChatCompletionResponse,
@@ -14,7 +14,7 @@ export class ChatCompletionService {
14
14
  private static instance: ChatCompletionService;
15
15
  private aiUsageService = AIUsageService.getInstance();
16
16
  private aiConfigService = AIConfigService.getInstance();
17
- private aiClientService = AIClientService.getInstance();
17
+ private openRouterProvider = OpenRouterProvider.getInstance();
18
18
 
19
19
  private constructor() {}
20
20
 
@@ -106,7 +106,7 @@ export class ChatCompletionService {
106
106
  };
107
107
 
108
108
  // Send request with automatic renewal and retry logic
109
- const response = await this.aiClientService.sendRequest((client) =>
109
+ const response = await this.openRouterProvider.sendRequest((client) =>
110
110
  client.chat.completions.create(request)
111
111
  );
112
112
 
@@ -173,7 +173,7 @@ export class ChatCompletionService {
173
173
  };
174
174
 
175
175
  // Send request with automatic renewal and retry logic
176
- const stream = await this.aiClientService.sendRequest((client) =>
176
+ const stream = await this.openRouterProvider.sendRequest((client) =>
177
177
  client.chat.completions.create(request)
178
178
  );
179
179
 
@@ -2,7 +2,7 @@ import OpenAI from 'openai';
2
2
 
3
3
  import { AIUsageService } from './ai-usage.service.js';
4
4
  import { AIConfigService } from './ai-config.service.js';
5
- import { AIClientService } from '@/providers/ai/openrouter.provider.js';
5
+ import { OpenRouterProvider } from '@/providers/ai/openrouter.provider.js';
6
6
  import type {
7
7
  AIConfigurationSchema,
8
8
  ImageGenerationRequest,
@@ -14,7 +14,7 @@ import { OpenRouterImageMessage } from '@/types/ai.js';
14
14
  export class ImageGenerationService {
15
15
  private static aiUsageService = AIUsageService.getInstance();
16
16
  private static aiConfigService = AIConfigService.getInstance();
17
- private static aiClientService = AIClientService.getInstance();
17
+ private static openRouterProvider = OpenRouterProvider.getInstance();
18
18
 
19
19
  /**
20
20
  * Validate model and get config
@@ -75,7 +75,7 @@ export class ImageGenerationService {
75
75
  };
76
76
 
77
77
  // Send request with automatic renewal and retry logic
78
- const response = (await this.aiClientService.sendRequest((client) =>
78
+ const response = (await this.openRouterProvider.sendRequest((client) =>
79
79
  client.chat.completions.create(
80
80
  request as OpenAI.Chat.ChatCompletionCreateParamsNonStreaming
81
81
  )
@@ -33,6 +33,7 @@ import {
33
33
  LinkedInUserInfo,
34
34
  DiscordUserInfo,
35
35
  XUserInfo,
36
+ AppleUserInfo,
36
37
  UserRecord,
37
38
  OAuthUserData,
38
39
  } from '@/types/auth.js';
@@ -42,6 +43,7 @@ import { AppError } from '@/api/middlewares/error.js';
42
43
  import { ERROR_CODES } from '@/types/error-constants.js';
43
44
  import { EmailService } from '@/services/email/email.service.js';
44
45
  import { XOAuthProvider } from '@/providers/oauth/x.provider.js';
46
+ import { AppleOAuthProvider } from '@/providers/oauth/apple.provider.js';
45
47
 
46
48
  /**
47
49
  * Simplified JWT-based auth service
@@ -62,6 +64,7 @@ export class AuthService {
62
64
  private facebookOAuthProvider: FacebookOAuthProvider;
63
65
  private microsoftOAuthProvider: MicrosoftOAuthProvider;
64
66
  private xOAuthProvider: XOAuthProvider;
67
+ private appleOAuthProvider: AppleOAuthProvider;
65
68
 
66
69
  private constructor() {
67
70
  this.adminEmail = process.env.ADMIN_EMAIL ?? '';
@@ -82,6 +85,7 @@ export class AuthService {
82
85
  this.facebookOAuthProvider = FacebookOAuthProvider.getInstance();
83
86
  this.microsoftOAuthProvider = MicrosoftOAuthProvider.getInstance();
84
87
  this.xOAuthProvider = XOAuthProvider.getInstance();
88
+ this.appleOAuthProvider = AppleOAuthProvider.getInstance();
85
89
 
86
90
  logger.info('AuthService initialized');
87
91
  }
@@ -670,6 +674,7 @@ export class AuthService {
670
674
  | MicrosoftUserInfo
671
675
  | FacebookUserInfo
672
676
  | XUserInfo
677
+ | AppleUserInfo
673
678
  | Record<string, unknown>
674
679
  ): Promise<CreateSessionResponse> {
675
680
  const pool = this.getPool();
@@ -777,6 +782,7 @@ export class AuthService {
777
782
  | MicrosoftUserInfo
778
783
  | FacebookUserInfo
779
784
  | XUserInfo
785
+ | AppleUserInfo
780
786
  | Record<string, unknown>,
781
787
  avatarUrl: string
782
788
  ): Promise<CreateSessionResponse> {
@@ -870,6 +876,8 @@ export class AuthService {
870
876
  return this.microsoftOAuthProvider.generateOAuthUrl(state);
871
877
  case 'x':
872
878
  return this.xOAuthProvider.generateOAuthUrl(state);
879
+ case 'apple':
880
+ return this.appleOAuthProvider.generateOAuthUrl(state);
873
881
  default:
874
882
  throw new Error(`OAuth provider ${provider} is not implemented yet.`);
875
883
  }
@@ -906,6 +914,9 @@ export class AuthService {
906
914
  case 'x':
907
915
  userData = await this.xOAuthProvider.handleCallback(payload);
908
916
  break;
917
+ case 'apple':
918
+ userData = await this.appleOAuthProvider.handleCallback(payload);
919
+ break;
909
920
  default:
910
921
  throw new Error(`OAuth provider ${provider} is not implemented yet.`);
911
922
  }
@@ -949,6 +960,9 @@ export class AuthService {
949
960
  case 'x':
950
961
  userData = this.xOAuthProvider.handleSharedCallback(payloadData);
951
962
  break;
963
+ case 'apple':
964
+ userData = this.appleOAuthProvider.handleSharedCallback(payloadData);
965
+ break;
952
966
  case 'microsoft':
953
967
  default:
954
968
  throw new Error(`OAuth provider ${provider} is not supported for shared callback.`);
@@ -268,15 +268,6 @@ export class DatabaseTableService {
268
268
  }
269
269
  }
270
270
 
271
- /**
272
- * Get all table schemas
273
- */
274
- async getAllTableSchemas(): Promise<GetTableSchemaResponse[]> {
275
- const tables = await this.listTables();
276
- const schemas = await Promise.all(tables.map((table) => this.getTableSchema(table)));
277
- return schemas;
278
- }
279
-
280
271
  /**
281
272
  * Get table schema
282
273
  */
@@ -0,0 +1,127 @@
1
+ import { DatabaseManager } from '@/infra/database/database.manager.js';
2
+ import type {
3
+ DatabaseFunctionsResponse,
4
+ DatabaseIndexesResponse,
5
+ DatabasePoliciesResponse,
6
+ DatabaseTriggersResponse,
7
+ } from '@insforge/shared-schemas';
8
+
9
+ export class DatabaseService {
10
+ private static instance: DatabaseService;
11
+ private dbManager = DatabaseManager.getInstance();
12
+
13
+ private constructor() {}
14
+
15
+ public static getInstance(): DatabaseService {
16
+ if (!DatabaseService.instance) {
17
+ DatabaseService.instance = new DatabaseService();
18
+ }
19
+ return DatabaseService.instance;
20
+ }
21
+
22
+ /**
23
+ * Get all database functions (excluding system and extension functions)
24
+ */
25
+ async getFunctions(): Promise<DatabaseFunctionsResponse> {
26
+ const pool = this.dbManager.getPool();
27
+
28
+ const result = await pool.query(`
29
+ SELECT
30
+ p.proname as "functionName",
31
+ pg_get_functiondef(p.oid) as "functionDef",
32
+ p.prokind as "kind"
33
+ FROM pg_proc p
34
+ JOIN pg_namespace n ON p.pronamespace = n.oid
35
+ WHERE n.nspname = 'public'
36
+ AND p.prokind IN ('f', 'p', 'w')
37
+ AND NOT EXISTS (
38
+ SELECT 1 FROM pg_depend d
39
+ JOIN pg_extension e ON d.refobjid = e.oid
40
+ WHERE d.objid = p.oid
41
+ )
42
+ ORDER BY p.proname
43
+ `);
44
+
45
+ return {
46
+ functions: result.rows,
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Get all indexes across all tables (excluding system tables)
52
+ */
53
+ async getIndexes(): Promise<DatabaseIndexesResponse> {
54
+ const pool = this.dbManager.getPool();
55
+
56
+ const result = await pool.query(`
57
+ SELECT
58
+ pi.tablename as "tableName",
59
+ pi.indexname as "indexName",
60
+ pi.indexdef as "indexDef",
61
+ idx.indisunique as "isUnique",
62
+ idx.indisprimary as "isPrimary"
63
+ FROM pg_indexes pi
64
+ JOIN pg_class cls ON cls.relname = pi.indexname
65
+ AND cls.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = pi.schemaname)
66
+ JOIN pg_index idx ON idx.indexrelid = cls.oid
67
+ WHERE pi.schemaname = 'public'
68
+ AND pi.tablename NOT LIKE '\\_%' ESCAPE '\\'
69
+ ORDER BY pi.tablename, pi.indexname
70
+ `);
71
+
72
+ return {
73
+ indexes: result.rows,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Get all RLS policies across all tables (excluding system tables)
79
+ */
80
+ async getPolicies(): Promise<DatabasePoliciesResponse> {
81
+ const pool = this.dbManager.getPool();
82
+
83
+ const result = await pool.query(`
84
+ SELECT
85
+ tablename as "tableName",
86
+ policyname as "policyName",
87
+ cmd,
88
+ roles,
89
+ qual,
90
+ with_check as "withCheck"
91
+ FROM pg_policies
92
+ WHERE schemaname = 'public'
93
+ AND tablename NOT LIKE '\\_%' ESCAPE '\\'
94
+ ORDER BY tablename, policyname
95
+ `);
96
+
97
+ return {
98
+ policies: result.rows,
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Get all triggers across all tables (excluding system tables)
104
+ */
105
+ async getTriggers(): Promise<DatabaseTriggersResponse> {
106
+ const pool = this.dbManager.getPool();
107
+
108
+ const result = await pool.query(`
109
+ SELECT
110
+ event_object_table as "tableName",
111
+ trigger_name as "triggerName",
112
+ action_timing as "actionTiming",
113
+ event_manipulation as "eventManipulation",
114
+ action_orientation as "actionOrientation",
115
+ action_condition as "actionCondition",
116
+ action_statement as "actionStatement"
117
+ FROM information_schema.triggers
118
+ WHERE event_object_schema = 'public'
119
+ AND event_object_table NOT LIKE '\\_%' ESCAPE '\\'
120
+ ORDER BY event_object_table, trigger_name
121
+ `);
122
+
123
+ return {
124
+ triggers: result.rows,
125
+ };
126
+ }
127
+ }
@@ -1,6 +1,7 @@
1
1
  import { EmailProvider } from '@/providers/email/base.provider.js';
2
2
  import { CloudEmailProvider } from '@/providers/email/cloud.provider.js';
3
3
  import { EmailTemplate } from '@/types/email.js';
4
+ import { SendRawEmailRequest } from '@insforge/shared-schemas';
4
5
  import logger from '@/utils/logger.js';
5
6
 
6
7
  /**
@@ -53,17 +54,14 @@ export class EmailService {
53
54
  }
54
55
 
55
56
  /**
56
- * Send raw email (if provider supports it)
57
- * @param to - Recipient email address
58
- * @param subject - Email subject
59
- * @param html - HTML email body
60
- * @param text - Plain text email body (optional)
57
+ * Send custom/raw email
58
+ * @param options - Email options (to, subject, html, cc, bcc, from, replyTo)
61
59
  */
62
- public async sendRaw(to: string, subject: string, html: string, text?: string): Promise<void> {
60
+ public async sendRaw(options: SendRawEmailRequest): Promise<void> {
63
61
  if (!this.provider.sendRaw) {
64
62
  throw new Error('Current email provider does not support raw email sending');
65
63
  }
66
- return this.provider.sendRaw(to, subject, html, text);
64
+ return this.provider.sendRaw(options);
67
65
  }
68
66
 
69
67
  /**
@@ -0,0 +1,3 @@
1
+ export { RealtimeChannelService } from './realtime-channel.service.js';
2
+ export { RealtimeAuthService } from './realtime-auth.service.js';
3
+ export { RealtimeMessageService } from './realtime-message.service.js';
@@ -0,0 +1,104 @@
1
+ import { Pool, PoolClient } from 'pg';
2
+ import { DatabaseManager } from '@/infra/database/database.manager.js';
3
+ import logger from '@/utils/logger.js';
4
+ import { RoleSchema } from '@insforge/shared-schemas';
5
+
6
+ /**
7
+ * Handles channel authorization by checking RLS policies on the messages table.
8
+ *
9
+ * Permission Model (Supabase pattern):
10
+ * - SELECT on messages = 'join' permission (can subscribe to channel)
11
+ * - INSERT on messages = 'send' permission (can publish to channel)
12
+ *
13
+ * Developers define RLS policies on realtime.messages that check:
14
+ * - current_setting('request.jwt.claim.sub', true) = user ID
15
+ * - current_setting('request.jwt.claim.role', true) = user role
16
+ * - channel_name for channel-specific access
17
+ */
18
+ export class RealtimeAuthService {
19
+ private static instance: RealtimeAuthService;
20
+ private pool: Pool | null = null;
21
+
22
+ private constructor() {}
23
+
24
+ static getInstance(): RealtimeAuthService {
25
+ if (!RealtimeAuthService.instance) {
26
+ RealtimeAuthService.instance = new RealtimeAuthService();
27
+ }
28
+ return RealtimeAuthService.instance;
29
+ }
30
+
31
+ private getPool(): Pool {
32
+ if (!this.pool) {
33
+ this.pool = DatabaseManager.getInstance().getPool();
34
+ }
35
+ return this.pool;
36
+ }
37
+
38
+ /**
39
+ * Check if user has permission to subscribe to a channel.
40
+ * Tests SELECT permission on channels table via RLS.
41
+ *
42
+ * @param channelName - The channel to check access for
43
+ * @param userId - The user ID (undefined for anonymous users)
44
+ * @param role - The database role to use (authenticated or anon)
45
+ * @returns true if user can subscribe, false otherwise
46
+ */
47
+ async checkSubscribePermission(
48
+ channelName: string,
49
+ userId: string | undefined,
50
+ role: RoleSchema
51
+ ): Promise<boolean> {
52
+ const client = await this.getPool().connect();
53
+ try {
54
+ // Begin transaction to ensure settings persist across queries
55
+ await client.query('BEGIN');
56
+ // Switch to specified role to enforce RLS policies
57
+ await client.query(`SET LOCAL ROLE ${role}`);
58
+ await this.setUserContext(client, userId, channelName);
59
+
60
+ // Test SELECT permission via RLS on channels table
61
+ const result = await client.query(
62
+ `SELECT 1 FROM realtime.channels
63
+ WHERE enabled = TRUE
64
+ AND (pattern = $1 OR $1 LIKE pattern)
65
+ LIMIT 1`,
66
+ [channelName]
67
+ );
68
+
69
+ // Commit transaction
70
+ await client.query('COMMIT');
71
+
72
+ // If query returns a row, user has permission
73
+ return result.rowCount !== null && result.rowCount > 0;
74
+ } catch (error) {
75
+ // Rollback transaction on error
76
+ await client.query('ROLLBACK').catch(() => {});
77
+ logger.debug('Subscribe permission denied', { channelName, userId, error });
78
+ return false;
79
+ } finally {
80
+ // Reset role back to default before releasing connection
81
+ await client.query('RESET ROLE');
82
+ client.release();
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Set user context variables for RLS policy evaluation.
88
+ * Can be used by other services that need to execute queries with user context.
89
+ */
90
+ async setUserContext(
91
+ client: PoolClient,
92
+ userId: string | undefined,
93
+ channelName: string
94
+ ): Promise<void> {
95
+ if (userId) {
96
+ await client.query("SELECT set_config('request.jwt.claim.sub', $1, true)", [userId]);
97
+ } else {
98
+ await client.query("SELECT set_config('request.jwt.claim.sub', '', true)");
99
+ }
100
+
101
+ // Set the channel being accessed (used by realtime.channel_name())
102
+ await client.query("SELECT set_config('realtime.channel_name', $1, true)", [channelName]);
103
+ }
104
+ }
@@ -0,0 +1,237 @@
1
+ import { Pool } from 'pg';
2
+ import { DatabaseManager } from '@/infra/database/database.manager.js';
3
+ import { AppError } from '@/api/middlewares/error.js';
4
+ import { ERROR_CODES } from '@/types/error-constants.js';
5
+ import logger from '@/utils/logger.js';
6
+ import type {
7
+ RealtimeChannel,
8
+ CreateChannelRequest,
9
+ UpdateChannelRequest,
10
+ RealtimeMetadataSchema,
11
+ RlsPolicy,
12
+ RealtimePermissionsResponse,
13
+ } from '@insforge/shared-schemas';
14
+
15
+ const SYSTEM_POLICIES = ['project_admin_policy'];
16
+
17
+ export class RealtimeChannelService {
18
+ private static instance: RealtimeChannelService;
19
+ private pool: Pool | null = null;
20
+
21
+ private constructor() {}
22
+
23
+ static getInstance(): RealtimeChannelService {
24
+ if (!RealtimeChannelService.instance) {
25
+ RealtimeChannelService.instance = new RealtimeChannelService();
26
+ }
27
+ return RealtimeChannelService.instance;
28
+ }
29
+
30
+ private getPool(): Pool {
31
+ if (!this.pool) {
32
+ this.pool = DatabaseManager.getInstance().getPool();
33
+ }
34
+ return this.pool;
35
+ }
36
+
37
+ async list(): Promise<RealtimeChannel[]> {
38
+ const result = await this.getPool().query(`
39
+ SELECT
40
+ id,
41
+ pattern,
42
+ description,
43
+ webhook_urls as "webhookUrls",
44
+ enabled,
45
+ created_at as "createdAt",
46
+ updated_at as "updatedAt"
47
+ FROM realtime.channels
48
+ ORDER BY created_at DESC
49
+ `);
50
+ return result.rows;
51
+ }
52
+
53
+ async getById(id: string): Promise<RealtimeChannel | null> {
54
+ const result = await this.getPool().query(
55
+ `SELECT
56
+ id,
57
+ pattern,
58
+ description,
59
+ webhook_urls as "webhookUrls",
60
+ enabled,
61
+ created_at as "createdAt",
62
+ updated_at as "updatedAt"
63
+ FROM realtime.channels
64
+ WHERE id = $1`,
65
+ [id]
66
+ );
67
+ return result.rows[0] || null;
68
+ }
69
+
70
+ /**
71
+ * Find a channel by name (exact match or wildcard pattern match).
72
+ * For wildcard patterns like "order:%", checks if channelName matches the pattern.
73
+ * Returns the matching channel if found and enabled, null otherwise.
74
+ */
75
+ async getByName(channelName: string): Promise<RealtimeChannel | null> {
76
+ const result = await this.getPool().query(
77
+ `SELECT
78
+ id,
79
+ pattern,
80
+ description,
81
+ webhook_urls as "webhookUrls",
82
+ enabled,
83
+ created_at as "createdAt",
84
+ updated_at as "updatedAt"
85
+ FROM realtime.channels
86
+ WHERE enabled = TRUE
87
+ AND (pattern = $1 OR $1 LIKE pattern)
88
+ ORDER BY pattern = $1 DESC
89
+ LIMIT 1`,
90
+ [channelName]
91
+ );
92
+ return result.rows[0] || null;
93
+ }
94
+
95
+ async create(input: CreateChannelRequest): Promise<RealtimeChannel> {
96
+ this.validateChannelPattern(input.pattern);
97
+
98
+ const result = await this.getPool().query(
99
+ `INSERT INTO realtime.channels (
100
+ pattern, description, webhook_urls, enabled
101
+ ) VALUES ($1, $2, $3, $4)
102
+ RETURNING
103
+ id,
104
+ pattern,
105
+ description,
106
+ webhook_urls as "webhookUrls",
107
+ enabled,
108
+ created_at as "createdAt",
109
+ updated_at as "updatedAt"`,
110
+ [input.pattern, input.description || null, input.webhookUrls || null, input.enabled ?? true]
111
+ );
112
+
113
+ logger.info('Realtime channel created', { pattern: input.pattern });
114
+ return result.rows[0];
115
+ }
116
+
117
+ async update(id: string, input: UpdateChannelRequest): Promise<RealtimeChannel> {
118
+ const existing = await this.getById(id);
119
+ if (!existing) {
120
+ throw new AppError('Channel not found', 404, ERROR_CODES.NOT_FOUND);
121
+ }
122
+
123
+ if (input.pattern) {
124
+ this.validateChannelPattern(input.pattern);
125
+ }
126
+
127
+ const result = await this.getPool().query(
128
+ `UPDATE realtime.channels
129
+ SET
130
+ pattern = COALESCE($2, pattern),
131
+ description = COALESCE($3, description),
132
+ webhook_urls = COALESCE($4, webhook_urls),
133
+ enabled = COALESCE($5, enabled)
134
+ WHERE id = $1
135
+ RETURNING
136
+ id,
137
+ pattern,
138
+ description,
139
+ webhook_urls as "webhookUrls",
140
+ enabled,
141
+ created_at as "createdAt",
142
+ updated_at as "updatedAt"`,
143
+ [id, input.pattern, input.description, input.webhookUrls, input.enabled]
144
+ );
145
+
146
+ logger.info('Realtime channel updated', { id });
147
+ return result.rows[0];
148
+ }
149
+
150
+ async delete(id: string): Promise<void> {
151
+ const existing = await this.getById(id);
152
+ if (!existing) {
153
+ throw new AppError('Channel not found', 404, ERROR_CODES.NOT_FOUND);
154
+ }
155
+
156
+ await this.getPool().query('DELETE FROM realtime.channels WHERE id = $1', [id]);
157
+ logger.info('Realtime channel deleted', { id, pattern: existing.pattern });
158
+ }
159
+
160
+ /**
161
+ * Get realtime metadata including channels and permissions
162
+ */
163
+ async getMetadata(): Promise<RealtimeMetadataSchema> {
164
+ const [channels, permissions] = await Promise.all([this.list(), this.getPermissions()]);
165
+
166
+ return {
167
+ channels,
168
+ permissions,
169
+ };
170
+ }
171
+
172
+ // ============================================================================
173
+ // Permissions Methods
174
+ // ============================================================================
175
+
176
+ /**
177
+ * Get RLS policies for a table in the realtime schema, excluding system policies
178
+ */
179
+ private async getPolicies(tableName: string): Promise<RlsPolicy[]> {
180
+ const result = await this.getPool().query(
181
+ `SELECT
182
+ policyname as "policyName",
183
+ tablename as "tableName",
184
+ cmd as "command",
185
+ roles,
186
+ qual as "using",
187
+ with_check as "withCheck"
188
+ FROM pg_policies
189
+ WHERE schemaname = 'realtime'
190
+ AND tablename = $1
191
+ ORDER BY policyname`,
192
+ [tableName]
193
+ );
194
+
195
+ // Filter out system policies
196
+ return result.rows.filter((policy) => !SYSTEM_POLICIES.includes(policy.policyName));
197
+ }
198
+
199
+ /**
200
+ * Get all realtime permissions (RLS policies for channels and messages tables)
201
+ *
202
+ * - Subscribe permission: RLS policies on realtime.channels (SELECT)
203
+ * - Publish permission: RLS policies on realtime.messages (INSERT)
204
+ */
205
+ async getPermissions(): Promise<RealtimePermissionsResponse> {
206
+ const [channelsPolicies, messagesPolicies] = await Promise.all([
207
+ this.getPolicies('channels'),
208
+ this.getPolicies('messages'),
209
+ ]);
210
+
211
+ return {
212
+ subscribe: {
213
+ policies: channelsPolicies,
214
+ },
215
+ publish: {
216
+ policies: messagesPolicies,
217
+ },
218
+ };
219
+ }
220
+
221
+ // ============================================================================
222
+ // Validation
223
+ // ============================================================================
224
+
225
+ private validateChannelPattern(pattern: string): void {
226
+ // Allow alphanumeric, colons, hyphens, and % for wildcards
227
+ // Note: underscore is not allowed as it's a SQL wildcard character
228
+ const validPattern = /^[a-zA-Z0-9-]+(:[a-zA-Z0-9%:-]+)*$/;
229
+ if (!validPattern.test(pattern)) {
230
+ throw new AppError(
231
+ 'Invalid channel pattern. Use alphanumeric characters, colons, hyphens, and % for wildcards.',
232
+ 400,
233
+ ERROR_CODES.INVALID_INPUT
234
+ );
235
+ }
236
+ }
237
+ }