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
@@ -15,6 +15,7 @@ import logger from '@/utils/logger.js';
15
15
  import { SocketManager } from '@/infra/socket/socket.manager.js';
16
16
  import { DataUpdateResourceType, ServerEvents } from '@/types/socket.js';
17
17
  import { successResponse } from '@/utils/response.js';
18
+ import { analyzeQuery, DatabaseResourceUpdate } from '@/utils/sql-parser.js';
18
19
 
19
20
  const router = Router();
20
21
  const dbAdvanceService = DatabaseAdvanceService.getInstance();
@@ -64,10 +65,17 @@ router.post(
64
65
  ip_address: req.ip,
65
66
  });
66
67
 
67
- const socket = SocketManager.getInstance();
68
- socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
69
- resource: DataUpdateResourceType.DATABASE,
70
- });
68
+ // Broadcast changes if any modifying statements detected
69
+ const changes = analyzeQuery(query);
70
+ if (changes.length > 0) {
71
+ const socket = SocketManager.getInstance();
72
+ socket.broadcastToRoom(
73
+ 'role:project_admin',
74
+ ServerEvents.DATA_UPDATE,
75
+ { resource: DataUpdateResourceType.DATABASE, data: { changes } },
76
+ 'system'
77
+ );
78
+ }
71
79
 
72
80
  successResponse(res, response);
73
81
  } catch (error: unknown) {
@@ -115,10 +123,17 @@ router.post('/rawsql', verifyAdmin, async (req: AuthRequest, res: Response, next
115
123
  ip_address: req.ip,
116
124
  });
117
125
 
118
- const socket = SocketManager.getInstance();
119
- socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
120
- resource: DataUpdateResourceType.DATABASE,
121
- });
126
+ // Broadcast changes if any modifying statements detected
127
+ const changes = analyzeQuery(query);
128
+ if (changes.length > 0) {
129
+ const socket = SocketManager.getInstance();
130
+ socket.broadcastToRoom(
131
+ 'role:project_admin',
132
+ ServerEvents.DATA_UPDATE,
133
+ { resource: DataUpdateResourceType.DATABASE, data: { changes } },
134
+ 'system'
135
+ );
136
+ }
122
137
 
123
138
  successResponse(res, response);
124
139
  } catch (error: unknown) {
@@ -235,12 +250,15 @@ router.post(
235
250
  });
236
251
 
237
252
  const socket = SocketManager.getInstance();
238
- socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
239
- resource: DataUpdateResourceType.RECORDS,
240
- data: {
241
- tableName: table,
253
+ socket.broadcastToRoom(
254
+ 'role:project_admin',
255
+ ServerEvents.DATA_UPDATE,
256
+ {
257
+ resource: DataUpdateResourceType.DATABASE,
258
+ data: { changes: [{ type: 'records', name: table }] as DatabaseResourceUpdate[] },
242
259
  },
243
- });
260
+ 'system'
261
+ );
244
262
 
245
263
  successResponse(res, response);
246
264
  } catch (error: unknown) {
@@ -302,9 +320,12 @@ router.post(
302
320
  });
303
321
 
304
322
  const socket = SocketManager.getInstance();
305
- socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
306
- resource: DataUpdateResourceType.DATABASE,
307
- });
323
+ socket.broadcastToRoom(
324
+ 'role:project_admin',
325
+ ServerEvents.DATA_UPDATE,
326
+ { resource: DataUpdateResourceType.DATABASE },
327
+ 'system'
328
+ );
308
329
 
309
330
  successResponse(res, response);
310
331
  } catch (error: unknown) {
@@ -1,13 +1,90 @@
1
- import { Router } from 'express';
1
+ import { Router, Response, NextFunction } from 'express';
2
2
  import { databaseTablesRouter } from './tables.routes.js';
3
3
  import { databaseRecordsRouter } from './records.routes.js';
4
4
  import databaseAdvanceRouter from './advance.routes.js';
5
+ import { DatabaseService } from '@/services/database/database.service.js';
6
+ import { verifyAdmin, AuthRequest } from '@/api/middlewares/auth.js';
7
+ import { successResponse } from '@/utils/response.js';
8
+ import logger from '@/utils/logger.js';
5
9
 
6
10
  const router = Router();
11
+ const databaseService = DatabaseService.getInstance();
7
12
 
8
13
  // Mount database sub-routes
9
14
  router.use('/tables', databaseTablesRouter);
10
15
  router.use('/records', databaseRecordsRouter);
11
16
  router.use('/advance', databaseAdvanceRouter);
12
17
 
18
+ /**
19
+ * Get all database functions
20
+ * GET /api/database/functions
21
+ */
22
+ router.get(
23
+ '/functions',
24
+ verifyAdmin,
25
+ async (_req: AuthRequest, res: Response, next: NextFunction) => {
26
+ try {
27
+ const response = await databaseService.getFunctions();
28
+ successResponse(res, response);
29
+ } catch (error: unknown) {
30
+ logger.warn('Get functions error:', error);
31
+ next(error);
32
+ }
33
+ }
34
+ );
35
+
36
+ /**
37
+ * Get all database indexes
38
+ * GET /api/database/indexes
39
+ */
40
+ router.get(
41
+ '/indexes',
42
+ verifyAdmin,
43
+ async (_req: AuthRequest, res: Response, next: NextFunction) => {
44
+ try {
45
+ const response = await databaseService.getIndexes();
46
+ successResponse(res, response);
47
+ } catch (error: unknown) {
48
+ logger.warn('Get indexes error:', error);
49
+ next(error);
50
+ }
51
+ }
52
+ );
53
+
54
+ /**
55
+ * Get all RLS policies
56
+ * GET /api/database/policies
57
+ */
58
+ router.get(
59
+ '/policies',
60
+ verifyAdmin,
61
+ async (_req: AuthRequest, res: Response, next: NextFunction) => {
62
+ try {
63
+ const response = await databaseService.getPolicies();
64
+ successResponse(res, response);
65
+ } catch (error: unknown) {
66
+ logger.warn('Get policies error:', error);
67
+ next(error);
68
+ }
69
+ }
70
+ );
71
+
72
+ /**
73
+ * Get all database triggers
74
+ * GET /api/database/triggers
75
+ */
76
+ router.get(
77
+ '/triggers',
78
+ verifyAdmin,
79
+ async (_req: AuthRequest, res: Response, next: NextFunction) => {
80
+ try {
81
+ const response = await databaseService.getTriggers();
82
+ successResponse(res, response);
83
+ } catch (error: unknown) {
84
+ logger.warn('Get triggers error:', error);
85
+ next(error);
86
+ }
87
+ }
88
+ );
89
+
13
90
  export default router;
@@ -14,6 +14,7 @@ import logger from '@/utils/logger.js';
14
14
  import { SecretService } from '@/services/secrets/secret.service.js';
15
15
  import { SocketManager } from '@/infra/socket/socket.manager.js';
16
16
  import { DataUpdateResourceType, ServerEvents } from '@/types/socket.js';
17
+ import { DatabaseResourceUpdate } from '@/utils/sql-parser.js';
17
18
 
18
19
  const router = Router();
19
20
  const secretService = SecretService.getInstance();
@@ -53,11 +54,7 @@ const postgrestAxios = axios.create({
53
54
  // Generate admin token once and reuse
54
55
  // If user request with api key, this token should be added automatically.
55
56
  const tokenManager = TokenManager.getInstance();
56
- const adminToken = tokenManager.generateToken({
57
- sub: 'project-admin-with-api-key',
58
- email: 'project-admin@email.com',
59
- role: 'project_admin',
60
- });
57
+ const adminToken = tokenManager.generateAdminToken();
61
58
 
62
59
  // anonymous users can access the database, postgREST does not require authentication, however we seed to unwrap session token for better auth, thus
63
60
  // we need to verify user token below.
@@ -209,12 +206,15 @@ const forwardToPostgrest = async (req: AuthRequest, res: Response, next: NextFun
209
206
  const mutationMethods = ['POST', 'DELETE'];
210
207
  if (mutationMethods.includes(req.method.toUpperCase())) {
211
208
  const socket = SocketManager.getInstance();
212
- socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
213
- resource: DataUpdateResourceType.RECORDS,
214
- data: {
215
- tableName: tableName,
209
+ socket.broadcastToRoom(
210
+ 'role:project_admin',
211
+ ServerEvents.DATA_UPDATE,
212
+ {
213
+ resource: DataUpdateResourceType.DATABASE,
214
+ data: { changes: [{ type: 'records', name: tableName }] as DatabaseResourceUpdate[] },
216
215
  },
217
- });
216
+ 'system'
217
+ );
218
218
  }
219
219
 
220
220
  successResponse(res, responseData, response.status);
@@ -59,20 +59,6 @@ router.post('/', verifyAdmin, async (req: AuthRequest, res: Response, next: Next
59
59
  }
60
60
  });
61
61
 
62
- // Get all table schemas
63
- router.get(
64
- '/schemas',
65
- verifyAdmin,
66
- async (_req: AuthRequest, res: Response, next: NextFunction) => {
67
- try {
68
- const schemas = await tableService.getAllTableSchemas();
69
- successResponse(res, schemas);
70
- } catch (error) {
71
- next(error);
72
- }
73
- }
74
- );
75
-
76
62
  // Get table schema
77
63
  router.get(
78
64
  '/:tableName/schema',
@@ -1,76 +1,75 @@
1
- import { Router, Request, Response, NextFunction } from 'express';
2
- import { readFile } from 'fs/promises';
3
- import path from 'path';
4
- import { fileURLToPath } from 'url';
5
- import { successResponse } from '@/utils/response.js';
6
- import { ERROR_CODES } from '@/types/error-constants.js';
7
- import { AppError } from '@/api/middlewares/error.js';
8
-
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = path.dirname(__filename);
11
-
12
- const router = Router();
13
-
14
- // Define available documentation files
15
- const DOCS_MAP: Record<string, string> = {
16
- instructions: 'insforge-instructions-sdk.md',
17
- 'db-sdk': 'core-concepts/database/sdk.mdx',
18
- 'auth-sdk': 'core-concepts/authentication/sdk.mdx',
19
- // UI Components - Framework-specific
20
- 'auth-components-react': 'core-concepts/authentication/ui-components/react.mdx',
21
- 'auth-components-nextjs': 'core-concepts/authentication/ui-components/nextjs.mdx',
22
- 'auth-components-react-router': 'core-concepts/authentication/ui-components/react-router.mdx',
23
- 'storage-sdk': 'core-concepts/storage/sdk.mdx',
24
- 'functions-sdk': 'core-concepts/functions/sdk.mdx',
25
- 'ai-integration-sdk': 'core-concepts/ai/sdk.mdx',
26
- };
27
-
28
- // GET /api/docs/:docType - Get specific documentation
29
- router.get('/:docType', async (req: Request, res: Response, next: NextFunction) => {
30
- try {
31
- const { docType } = req.params;
32
-
33
- // Validate doc type
34
- const docFileName = DOCS_MAP[docType];
35
- if (!docFileName) {
36
- throw new AppError('Documentation not found', 404, ERROR_CODES.NOT_FOUND);
37
- }
38
-
39
- // Read the documentation file
40
- // PROJECT_ROOT is set in the docker-compose.yml file to point to the InsForge directory
41
- const projectRoot = process.env.PROJECT_ROOT || path.resolve(__dirname, '../../../..');
42
- const filePath = path.join(projectRoot, 'docs', docFileName);
43
- const content = await readFile(filePath, 'utf-8');
44
-
45
- // Traditional REST: return documentation directly
46
- return successResponse(res, {
47
- type: docType,
48
- content,
49
- });
50
- } catch (error) {
51
- // If file doesn't exist or other error
52
- if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
53
- next(new AppError('Documentation file not found', 404, ERROR_CODES.NOT_FOUND));
54
- } else {
55
- next(error);
56
- }
57
- }
58
- });
59
-
60
- // GET /api/docs - List available documentation
61
- router.get('/', (_req: Request, res: Response, next: NextFunction) => {
62
- try {
63
- const available = Object.keys(DOCS_MAP).map((key) => ({
64
- type: key,
65
- filename: DOCS_MAP[key],
66
- endpoint: `/api/docs/${key}`,
67
- }));
68
-
69
- // Traditional REST: return list directly
70
- return successResponse(res, available);
71
- } catch (error) {
72
- next(error);
73
- }
74
- });
75
-
76
- export { router as docsRouter };
1
+ import { Router, Request, Response, NextFunction } from 'express';
2
+ import { readFile } from 'fs/promises';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { successResponse } from '@/utils/response.js';
6
+ import { ERROR_CODES } from '@/types/error-constants.js';
7
+ import { AppError } from '@/api/middlewares/error.js';
8
+ import { DocTypeSchema, docTypeSchema } from '@insforge/shared-schemas';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ const router = Router();
14
+
15
+ // Define available documentation files
16
+ const DOCS_MAP: Record<DocTypeSchema, string> = {
17
+ instructions: 'insforge-instructions-sdk.md',
18
+ 'db-sdk': 'core-concepts/database/sdk.mdx',
19
+ // 'auth-sdk': 'core-concepts/authentication/sdk.mdx',
20
+ // UI Components - Framework-specific
21
+ 'auth-components-react': 'core-concepts/authentication/ui-components/react.mdx',
22
+ // 'auth-components-nextjs': 'core-concepts/authentication/ui-components/nextjs.mdx',
23
+ // 'auth-components-react-router': 'core-concepts/authentication/ui-components/react-router.mdx',
24
+ 'storage-sdk': 'core-concepts/storage/sdk.mdx',
25
+ 'functions-sdk': 'core-concepts/functions/sdk.mdx',
26
+ 'ai-integration-sdk': 'core-concepts/ai/sdk.mdx',
27
+ 'real-time': 'agent-docs/real-time.md',
28
+ };
29
+
30
+ // GET /api/docs/:docType - Get specific documentation
31
+ router.get('/:docType', async (req: Request, res: Response, next: NextFunction) => {
32
+ try {
33
+ const { docType } = req.params;
34
+
35
+ // Validate doc type using Zod enum
36
+ const parsed = docTypeSchema.safeParse(docType);
37
+ if (!parsed.success) {
38
+ throw new AppError('Documentation not found', 404, ERROR_CODES.NOT_FOUND);
39
+ }
40
+
41
+ const docFileName = DOCS_MAP[parsed.data];
42
+
43
+ // Read the documentation file
44
+ // PROJECT_ROOT is set in the docker-compose.yml file to point to the InsForge directory
45
+ const projectRoot = process.env.PROJECT_ROOT || path.resolve(__dirname, '../../../..');
46
+ const filePath = path.join(projectRoot, 'docs', docFileName);
47
+ const content = await readFile(filePath, 'utf-8');
48
+
49
+ // Traditional REST: return documentation directly
50
+ return successResponse(res, {
51
+ type: docType,
52
+ content,
53
+ });
54
+ } catch (error) {
55
+ next(error);
56
+ }
57
+ });
58
+
59
+ // GET /api/docs - List available documentation
60
+ router.get('/', (_req: Request, res: Response, next: NextFunction) => {
61
+ try {
62
+ const available = (Object.keys(DOCS_MAP) as DocTypeSchema[]).map((key) => ({
63
+ type: key,
64
+ filename: DOCS_MAP[key],
65
+ endpoint: `/api/docs/${key}`,
66
+ }));
67
+
68
+ // Traditional REST: return list directly
69
+ return successResponse(res, available);
70
+ } catch (error) {
71
+ next(error);
72
+ }
73
+ });
74
+
75
+ export { router as docsRouter };
@@ -0,0 +1,35 @@
1
+ import { Router, Response, NextFunction } from 'express';
2
+ import { AuthRequest, verifyUser } from '@/api/middlewares/auth.js';
3
+ import { EmailService } from '@/services/email/email.service.js';
4
+ import { AppError } from '@/api/middlewares/error.js';
5
+ import { ERROR_CODES } from '@/types/error-constants.js';
6
+ import { sendRawEmailRequestSchema } from '@insforge/shared-schemas';
7
+ import { successResponse } from '@/utils/response.js';
8
+
9
+ const router = Router();
10
+ const emailService = EmailService.getInstance();
11
+
12
+ /**
13
+ * POST /api/email/send-raw
14
+ * Send a raw/custom email with explicit to, subject, and body
15
+ */
16
+ router.post(
17
+ '/send-raw',
18
+ verifyUser,
19
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
20
+ try {
21
+ const validation = sendRawEmailRequestSchema.safeParse(req.body);
22
+ if (!validation.success) {
23
+ throw new AppError(JSON.stringify(validation.error.issues), 400, ERROR_CODES.INVALID_INPUT);
24
+ }
25
+
26
+ await emailService.sendRaw(validation.data);
27
+
28
+ successResponse(res, {});
29
+ } catch (error) {
30
+ next(error);
31
+ }
32
+ }
33
+ );
34
+
35
+ export const emailRouter = router;
@@ -76,9 +76,12 @@ router.post('/', verifyAdmin, async (req: AuthRequest, res: Response, next: Next
76
76
  });
77
77
 
78
78
  const socket = SocketManager.getInstance();
79
- socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
80
- resource: DataUpdateResourceType.FUNCTIONS,
81
- });
79
+ socket.broadcastToRoom(
80
+ 'role:project_admin',
81
+ ServerEvents.DATA_UPDATE,
82
+ { resource: DataUpdateResourceType.FUNCTIONS },
83
+ 'system'
84
+ );
82
85
 
83
86
  successResponse(
84
87
  res,
@@ -126,12 +129,12 @@ router.put('/:slug', verifyAdmin, async (req: AuthRequest, res: Response, next:
126
129
  });
127
130
 
128
131
  const socket = SocketManager.getInstance();
129
- socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
130
- resource: DataUpdateResourceType.FUNCTIONS,
131
- data: {
132
- slug,
133
- },
134
- });
132
+ socket.broadcastToRoom(
133
+ 'role:project_admin',
134
+ ServerEvents.DATA_UPDATE,
135
+ { resource: DataUpdateResourceType.FUNCTIONS, data: { slug } },
136
+ 'system'
137
+ );
135
138
 
136
139
  successResponse(res, {
137
140
  success: true,
@@ -171,9 +174,12 @@ router.delete(
171
174
  });
172
175
 
173
176
  const socket = SocketManager.getInstance();
174
- socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
175
- resource: DataUpdateResourceType.FUNCTIONS,
176
- });
177
+ socket.broadcastToRoom(
178
+ 'role:project_admin',
179
+ ServerEvents.DATA_UPDATE,
180
+ { resource: DataUpdateResourceType.FUNCTIONS },
181
+ 'system'
182
+ );
177
183
 
178
184
  successResponse(res, {
179
185
  success: true,
@@ -4,6 +4,7 @@ import { AuthService } from '@/services/auth/auth.service.js';
4
4
  import { StorageService } from '@/services/storage/storage.service.js';
5
5
  import { AIConfigService } from '@/services/ai/ai-config.service.js';
6
6
  import { FunctionService } from '@/services/functions/function.service.js';
7
+ import { RealtimeChannelService } from '@/services/realtime/realtime-channel.service.js';
7
8
  import { verifyAdmin, AuthRequest } from '@/api/middlewares/auth.js';
8
9
  import { successResponse } from '@/utils/response.js';
9
10
  import { ERROR_CODES } from '@/types/error-constants.js';
@@ -16,6 +17,7 @@ const router = Router();
16
17
  const authService = AuthService.getInstance();
17
18
  const storageService = StorageService.getInstance();
18
19
  const functionService = FunctionService.getInstance();
20
+ const realtimeChannelService = RealtimeChannelService.getInstance();
19
21
  const dbManager = DatabaseManager.getInstance();
20
22
  const dbAdvanceService = DatabaseAdvanceService.getInstance();
21
23
  const aiConfigService = AIConfigService.getInstance();
@@ -104,6 +106,16 @@ router.get('/functions', async (_req: AuthRequest, res: Response, next: NextFunc
104
106
  }
105
107
  });
106
108
 
109
+ // Get realtime metadata
110
+ router.get('/realtime', async (_req: AuthRequest, res: Response, next: NextFunction) => {
111
+ try {
112
+ const realtimeMetadata = await realtimeChannelService.getMetadata();
113
+ successResponse(res, realtimeMetadata);
114
+ } catch (error) {
115
+ next(error);
116
+ }
117
+ });
118
+
107
119
  // Get API key (admin only)
108
120
  router.get('/api-key', async (req: AuthRequest, res: Response, next: NextFunction) => {
109
121
  try {
@@ -0,0 +1,81 @@
1
+ import { Router, Response, NextFunction } from 'express';
2
+ import { verifyAdmin, AuthRequest } from '@/api/middlewares/auth.js';
3
+ import { RealtimeChannelService } from '@/services/realtime/realtime-channel.service.js';
4
+ import { successResponse } from '@/utils/response.js';
5
+ import { AppError } from '@/api/middlewares/error.js';
6
+ import { ERROR_CODES } from '@/types/error-constants.js';
7
+ import { createChannelRequestSchema, updateChannelRequestSchema } from '@insforge/shared-schemas';
8
+
9
+ const router = Router();
10
+ const channelService = RealtimeChannelService.getInstance();
11
+
12
+ // List all channels
13
+ router.get('/', verifyAdmin, async (_req: AuthRequest, res: Response, next: NextFunction) => {
14
+ try {
15
+ const channels = await channelService.list();
16
+ successResponse(res, channels);
17
+ } catch (error) {
18
+ next(error);
19
+ }
20
+ });
21
+
22
+ // Get channel by ID
23
+ router.get('/:id', verifyAdmin, async (req: AuthRequest, res: Response, next: NextFunction) => {
24
+ try {
25
+ const channel = await channelService.getById(req.params.id);
26
+ if (!channel) {
27
+ throw new AppError('Channel not found', 404, ERROR_CODES.NOT_FOUND);
28
+ }
29
+ successResponse(res, channel);
30
+ } catch (error) {
31
+ next(error);
32
+ }
33
+ });
34
+
35
+ // Create a channel
36
+ router.post('/', verifyAdmin, async (req: AuthRequest, res: Response, next: NextFunction) => {
37
+ try {
38
+ const validation = createChannelRequestSchema.safeParse(req.body);
39
+ if (!validation.success) {
40
+ throw new AppError(
41
+ validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', '),
42
+ 400,
43
+ ERROR_CODES.INVALID_INPUT
44
+ );
45
+ }
46
+ const channel = await channelService.create(validation.data);
47
+ successResponse(res, channel, 201);
48
+ } catch (error) {
49
+ next(error);
50
+ }
51
+ });
52
+
53
+ // Update a channel
54
+ router.put('/:id', verifyAdmin, async (req: AuthRequest, res: Response, next: NextFunction) => {
55
+ try {
56
+ const validation = updateChannelRequestSchema.safeParse(req.body);
57
+ if (!validation.success) {
58
+ throw new AppError(
59
+ validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', '),
60
+ 400,
61
+ ERROR_CODES.INVALID_INPUT
62
+ );
63
+ }
64
+ const channel = await channelService.update(req.params.id, validation.data);
65
+ successResponse(res, channel);
66
+ } catch (error) {
67
+ next(error);
68
+ }
69
+ });
70
+
71
+ // Delete a channel
72
+ router.delete('/:id', verifyAdmin, async (req: AuthRequest, res: Response, next: NextFunction) => {
73
+ try {
74
+ await channelService.delete(req.params.id);
75
+ successResponse(res, { message: 'Channel deleted' });
76
+ } catch (error) {
77
+ next(error);
78
+ }
79
+ });
80
+
81
+ export { router as channelsRouter };
@@ -0,0 +1,12 @@
1
+ import { Router } from 'express';
2
+ import { channelsRouter } from './channels.routes.js';
3
+ import { messagesRouter } from './messages.routes.js';
4
+ import { permissionsRouter } from './permissions.routes.js';
5
+
6
+ const router = Router();
7
+
8
+ router.use('/channels', channelsRouter);
9
+ router.use('/messages', messagesRouter);
10
+ router.use('/permissions', permissionsRouter);
11
+
12
+ export { router as realtimeRouter };
@@ -0,0 +1,48 @@
1
+ import { Router, Response, NextFunction } from 'express';
2
+ import { verifyAdmin, AuthRequest } from '@/api/middlewares/auth.js';
3
+ import { RealtimeMessageService } from '@/services/realtime/realtime-message.service.js';
4
+ import { successResponse } from '@/utils/response.js';
5
+ import { AppError } from '@/api/middlewares/error.js';
6
+ import { ERROR_CODES } from '@/types/error-constants.js';
7
+ import { listMessagesRequestSchema, messageStatsRequestSchema } from '@insforge/shared-schemas';
8
+
9
+ const router = Router();
10
+ const messageService = RealtimeMessageService.getInstance();
11
+
12
+ // List messages
13
+ router.get('/', verifyAdmin, async (req: AuthRequest, res: Response, next: NextFunction) => {
14
+ try {
15
+ const validation = listMessagesRequestSchema.safeParse(req.query);
16
+ if (!validation.success) {
17
+ throw new AppError(
18
+ validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', '),
19
+ 400,
20
+ ERROR_CODES.INVALID_INPUT
21
+ );
22
+ }
23
+ const messages = await messageService.list(validation.data);
24
+ successResponse(res, messages);
25
+ } catch (error) {
26
+ next(error);
27
+ }
28
+ });
29
+
30
+ // Get message statistics
31
+ router.get('/stats', verifyAdmin, async (req: AuthRequest, res: Response, next: NextFunction) => {
32
+ try {
33
+ const validation = messageStatsRequestSchema.safeParse(req.query);
34
+ if (!validation.success) {
35
+ throw new AppError(
36
+ validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', '),
37
+ 400,
38
+ ERROR_CODES.INVALID_INPUT
39
+ );
40
+ }
41
+ const stats = await messageService.getStats(validation.data);
42
+ successResponse(res, stats);
43
+ } catch (error) {
44
+ next(error);
45
+ }
46
+ });
47
+
48
+ export { router as messagesRouter };