insforge 1.3.0 → 1.4.8

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 (269) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/auth/package.json +5 -3
  3. package/auth/src/lib/broadcastService.ts +115 -117
  4. package/auth/src/lib/insforge.ts +8 -0
  5. package/auth/src/main.tsx +2 -4
  6. package/auth/src/pages/SignInPage.tsx +60 -60
  7. package/auth/src/pages/SignUpPage.tsx +60 -60
  8. package/auth/src/pages/VerifyEmailPage.tsx +18 -0
  9. package/auth/tsconfig.json +2 -1
  10. package/backend/package.json +10 -6
  11. package/backend/src/api/middlewares/rate-limiters.ts +127 -127
  12. package/backend/src/api/routes/ai/index.routes.ts +475 -468
  13. package/backend/src/api/routes/auth/index.routes.ts +85 -32
  14. package/backend/src/api/routes/auth/oauth.routes.ts +11 -6
  15. package/backend/src/api/routes/database/index.routes.ts +2 -0
  16. package/backend/src/api/routes/database/records.routes.ts +39 -175
  17. package/backend/src/api/routes/database/rpc.routes.ts +69 -0
  18. package/backend/src/api/routes/deployments/index.routes.ts +192 -0
  19. package/backend/src/api/routes/docs/index.routes.ts +3 -2
  20. package/backend/src/api/routes/email/index.routes.ts +35 -35
  21. package/backend/src/api/routes/functions/index.routes.ts +3 -3
  22. package/backend/src/api/routes/metadata/index.routes.ts +26 -0
  23. package/backend/src/api/routes/webhooks/index.routes.ts +109 -0
  24. package/backend/src/infra/database/database.manager.ts +0 -10
  25. package/backend/src/infra/database/migrations/018_schema-rework.sql +441 -0
  26. package/backend/src/infra/database/migrations/019_create-deployments-table.sql +36 -0
  27. package/backend/src/infra/database/migrations/020_add-audio-modality.sql +11 -0
  28. package/backend/src/infra/database/migrations/bootstrap/bootstrap-migrations.js +103 -0
  29. package/backend/src/infra/security/token.manager.ts +1 -4
  30. package/backend/src/providers/ai/openrouter.provider.ts +12 -3
  31. package/backend/src/providers/database/base.provider.ts +39 -0
  32. package/backend/src/providers/database/cloud.provider.ts +159 -0
  33. package/backend/src/providers/deployments/vercel.provider.ts +516 -0
  34. package/backend/src/server.ts +19 -7
  35. package/backend/src/services/ai/ai-config.service.ts +6 -6
  36. package/backend/src/services/ai/ai-model.service.ts +60 -60
  37. package/backend/src/services/ai/ai-usage.service.ts +7 -7
  38. package/backend/src/services/ai/chat-completion.service.ts +415 -220
  39. package/backend/src/services/ai/helpers.ts +64 -64
  40. package/backend/src/services/ai/index.ts +13 -13
  41. package/backend/src/services/auth/auth-config.service.ts +4 -4
  42. package/backend/src/services/auth/auth-otp.service.ts +6 -6
  43. package/backend/src/services/auth/auth.service.ts +134 -74
  44. package/backend/src/services/auth/index.ts +4 -4
  45. package/backend/src/services/auth/oauth-config.service.ts +12 -12
  46. package/backend/src/services/database/database-advance.service.ts +19 -55
  47. package/backend/src/services/database/database-table.service.ts +38 -85
  48. package/backend/src/services/database/postgrest-proxy.service.ts +165 -0
  49. package/backend/src/services/deployments/deployment.service.ts +693 -0
  50. package/backend/src/services/functions/function.service.ts +61 -41
  51. package/backend/src/services/logs/audit.service.ts +10 -10
  52. package/backend/src/services/secrets/secret.service.ts +101 -27
  53. package/backend/src/services/storage/storage.service.ts +30 -30
  54. package/backend/src/services/usage/usage.service.ts +6 -6
  55. package/backend/src/types/ai.ts +8 -0
  56. package/backend/src/types/auth.ts +5 -1
  57. package/backend/src/types/database.ts +2 -0
  58. package/backend/src/types/deployments.ts +33 -0
  59. package/backend/src/types/storage.ts +1 -1
  60. package/backend/src/types/webhooks.ts +45 -0
  61. package/backend/src/utils/cookies.ts +34 -35
  62. package/backend/src/utils/environment.ts +0 -14
  63. package/backend/src/utils/s3-config-loader.ts +64 -64
  64. package/backend/src/utils/seed.ts +334 -301
  65. package/backend/src/utils/sql-parser.ts +126 -0
  66. package/backend/src/utils/utils.ts +114 -114
  67. package/backend/src/utils/validations.ts +10 -10
  68. package/backend/tests/local/test-rpc.sh +141 -0
  69. package/backend/tests/local/test-secrets.sh +1 -1
  70. package/backend/tests/manual/test-ai-model-plugins.sh +258 -0
  71. package/backend/tests/manual/test-rawsql-modes.sh +24 -24
  72. package/backend/tests/unit/database-advance.test.ts +326 -0
  73. package/backend/tests/unit/helpers.test.ts +2 -2
  74. package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +13 -10
  75. package/docker-compose.prod.yml +1 -1
  76. package/docker-compose.yml +1 -1
  77. package/docs/agent-docs/deployment.md +79 -0
  78. package/docs/changelog.mdx +165 -72
  79. package/docs/core-concepts/ai/architecture.mdx +1 -23
  80. package/docs/core-concepts/ai/sdk.mdx +26 -1
  81. package/docs/core-concepts/authentication/architecture.mdx +6 -8
  82. package/docs/core-concepts/authentication/sdk.mdx +387 -91
  83. package/docs/core-concepts/authentication/ui-components/customization.mdx +460 -256
  84. package/docs/core-concepts/authentication/ui-components/nextjs.mdx +50 -24
  85. package/docs/core-concepts/authentication/ui-components/react-router.mdx +18 -19
  86. package/docs/core-concepts/authentication/ui-components/react.mdx +26 -19
  87. package/docs/core-concepts/database/architecture.mdx +58 -21
  88. package/docs/core-concepts/database/pgvector.mdx +138 -0
  89. package/docs/core-concepts/database/sdk.mdx +17 -17
  90. package/docs/core-concepts/deployments/architecture.mdx +152 -0
  91. package/docs/core-concepts/email/architecture.mdx +4 -2
  92. package/docs/core-concepts/functions/architecture.mdx +1 -1
  93. package/docs/core-concepts/functions/sdk.mdx +0 -1
  94. package/docs/core-concepts/realtime/architecture.mdx +1 -1
  95. package/docs/core-concepts/storage/architecture.mdx +1 -1
  96. package/docs/core-concepts/storage/sdk.mdx +25 -25
  97. package/docs/docs.json +14 -6
  98. package/docs/favicon.png +0 -0
  99. package/docs/favicon.svg +3 -18
  100. package/docs/images/changelog/dec-2025/apple-oauth.mp4 +0 -0
  101. package/docs/images/changelog/dec-2025/moreModels.png +0 -0
  102. package/docs/images/changelog/dec-2025/multi-region.webp +0 -0
  103. package/docs/images/changelog/dec-2025/postgres-connection.webp +0 -0
  104. package/docs/images/changelog/dec-2025/realtime2.png +0 -0
  105. package/docs/images/mcp-setup/CC-MCP-1.mp4 +0 -0
  106. package/docs/images/mcp-setup/CC-MCP-2.mp4 +0 -0
  107. package/docs/images/mcp-setup/Cursor-MCP-1.mp4 +0 -0
  108. package/docs/images/mcp-setup/Cursor-MCP-2.mp4 +0 -0
  109. package/docs/images/mcp-setup/Cursor-MCP-3.mp4 +0 -0
  110. package/docs/images/mcp-setup/claude-code-connect.png +0 -0
  111. package/docs/images/mcp-setup/cline-1.png +0 -0
  112. package/docs/images/mcp-setup/cline-2.png +0 -0
  113. package/docs/images/mcp-setup/cline-3.png +0 -0
  114. package/docs/images/mcp-setup/connect-project.png +0 -0
  115. package/docs/images/mcp-setup/copilot-1.png +0 -0
  116. package/docs/images/mcp-setup/copilot-2.png +0 -0
  117. package/docs/images/mcp-setup/copilot-3.png +0 -0
  118. package/docs/images/mcp-setup/mcp-json-1.png +0 -0
  119. package/docs/images/mcp-setup/mcp-json-2.png +0 -0
  120. package/docs/images/mcp-setup/qoder-1.png +0 -0
  121. package/docs/images/mcp-setup/qoder-2.png +0 -0
  122. package/docs/images/mcp-setup/roocode-1.png +0 -0
  123. package/docs/images/mcp-setup/roocode-2.png +0 -0
  124. package/docs/images/mcp-setup/trae-1.png +0 -0
  125. package/docs/images/mcp-setup/trae-2.png +0 -0
  126. package/docs/images/mcp-setup/trae-3.png +0 -0
  127. package/docs/images/mcp-setup/trae-4.png +0 -0
  128. package/docs/images/mcp-setup/trae-5.png +0 -0
  129. package/docs/images/mcp-setup/windsurf-1.png +0 -0
  130. package/docs/images/mcp-setup/windsurf-2.png +0 -0
  131. package/docs/insforge-instructions-sdk.md +7 -3
  132. package/docs/introduction.mdx +9 -8
  133. package/docs/mcp-setup.mdx +332 -0
  134. package/docs/oauth-server.mdx +563 -0
  135. package/docs/partnership.mdx +79 -10
  136. package/docs/quickstart.mdx +1 -1
  137. package/docs/vscode-extension.mdx +74 -0
  138. package/eslint.config.js +1 -0
  139. package/examples/response-examples.md +1 -1
  140. package/frontend/package.json +1 -1
  141. package/frontend/src/App.tsx +8 -3
  142. package/frontend/src/assets/logos/antigravity.svg +1 -0
  143. package/frontend/src/assets/logos/copilot.svg +10 -0
  144. package/frontend/src/assets/logos/deepseek.svg +139 -0
  145. package/frontend/src/assets/logos/kiro.svg +9 -0
  146. package/frontend/src/assets/logos/qoder.svg +4 -0
  147. package/frontend/src/assets/logos/qwen.svg +15 -0
  148. package/frontend/src/components/CodeBlock.tsx +2 -2
  149. package/frontend/src/components/ConnectCTA.tsx +3 -2
  150. package/frontend/src/components/datagrid/DataGrid.tsx +90 -62
  151. package/frontend/src/components/datagrid/datagridTypes.tsx +2 -1
  152. package/frontend/src/components/datagrid/index.ts +1 -1
  153. package/frontend/src/components/index.ts +0 -1
  154. package/frontend/src/components/layout/AppHeader.tsx +4 -27
  155. package/frontend/src/components/layout/AppSidebar.tsx +85 -100
  156. package/frontend/src/components/layout/Layout.tsx +34 -32
  157. package/frontend/src/components/layout/PrimaryMenu.tsx +12 -4
  158. package/frontend/src/components/radix/Select.tsx +151 -151
  159. package/frontend/src/features/ai/components/AIConfigCard.tsx +200 -200
  160. package/frontend/src/features/ai/components/AIEmptyState.tsx +23 -23
  161. package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +102 -101
  162. package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -135
  163. package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -51
  164. package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -118
  165. package/frontend/src/features/ai/components/index.ts +6 -6
  166. package/frontend/src/features/ai/helpers.ts +147 -141
  167. package/frontend/src/features/ai/pages/AIPage.tsx +166 -166
  168. package/frontend/src/features/auth/components/AuthPreview.tsx +96 -96
  169. package/frontend/src/features/auth/components/UsersDataGrid.tsx +55 -31
  170. package/frontend/src/features/auth/components/index.ts +5 -5
  171. package/frontend/src/features/auth/pages/AuthMethodsPage.tsx +275 -275
  172. package/frontend/src/features/dashboard/pages/DashboardPage.tsx +1 -1
  173. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +0 -2
  174. package/frontend/src/features/database/components/ForeignKeyCell.tsx +38 -11
  175. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +18 -8
  176. package/frontend/src/features/database/components/LinkRecordModal.tsx +61 -13
  177. package/frontend/src/features/database/components/RecordFormField.tsx +1 -1
  178. package/frontend/src/features/database/components/TableSidebar.tsx +0 -3
  179. package/frontend/src/features/database/components/TablesEmptyState.tsx +1 -1
  180. package/frontend/src/features/database/components/TemplatePreview.tsx +1 -2
  181. package/frontend/src/features/database/constants.ts +16 -28
  182. package/frontend/src/features/database/hooks/useCSVImport.ts +3 -2
  183. package/frontend/src/features/database/hooks/useRawSQL.ts +3 -2
  184. package/frontend/src/features/database/hooks/useTables.ts +5 -7
  185. package/frontend/src/features/database/pages/FunctionsPage.tsx +0 -5
  186. package/frontend/src/features/database/pages/IndexesPage.tsx +0 -5
  187. package/frontend/src/features/database/pages/PoliciesPage.tsx +0 -5
  188. package/frontend/src/features/database/pages/SQLEditorPage.tsx +2 -2
  189. package/frontend/src/features/database/pages/TriggersPage.tsx +0 -5
  190. package/frontend/src/features/database/services/advance.service.ts +1 -15
  191. package/frontend/src/features/database/services/record.service.ts +4 -20
  192. package/frontend/src/features/database/services/table.service.ts +1 -4
  193. package/frontend/src/features/database/templates/ai-chatbot.ts +6 -6
  194. package/frontend/src/features/database/templates/ecommerce-platform.ts +2 -2
  195. package/frontend/src/features/database/templates/instagram-clone.ts +10 -10
  196. package/frontend/src/features/database/templates/notion-clone.ts +8 -8
  197. package/frontend/src/features/database/templates/reddit-clone.ts +10 -10
  198. package/frontend/src/features/deployments/components/DeploymentRow.tsx +93 -0
  199. package/frontend/src/features/deployments/components/DeploymentsEmptyState.tsx +15 -0
  200. package/frontend/src/features/deployments/hooks/useDeployments.ts +157 -0
  201. package/frontend/src/features/deployments/pages/DeploymentsPage.tsx +318 -0
  202. package/frontend/src/features/deployments/services/deployments.service.ts +63 -0
  203. package/frontend/src/features/functions/components/FunctionRow.tsx +72 -72
  204. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -56
  205. package/frontend/src/features/functions/components/SecretRow.tsx +3 -3
  206. package/frontend/src/features/functions/components/index.ts +5 -5
  207. package/frontend/src/features/functions/hooks/useFunctions.ts +5 -4
  208. package/frontend/src/features/functions/hooks/useSecrets.ts +6 -9
  209. package/frontend/src/features/functions/pages/SecretsPage.tsx +118 -118
  210. package/frontend/src/features/functions/services/function.service.ts +8 -25
  211. package/frontend/src/features/functions/services/secret.service.ts +23 -41
  212. package/frontend/src/features/login/pages/CloudLoginPage.tsx +125 -118
  213. package/frontend/src/features/logs/components/LogDetailPanel.tsx +41 -0
  214. package/frontend/src/features/logs/components/LogsDataGrid.tsx +32 -1
  215. package/frontend/src/features/logs/components/index.ts +1 -0
  216. package/frontend/src/features/logs/pages/LogsPage.tsx +36 -6
  217. package/frontend/src/features/onboard/components/ApiCredentialsSection.tsx +59 -0
  218. package/frontend/src/features/onboard/components/ConnectionStringSection.tsx +180 -0
  219. package/frontend/src/features/onboard/components/McpConnectionSection.tsx +159 -0
  220. package/frontend/src/features/onboard/components/OnboardingController.tsx +68 -0
  221. package/frontend/src/features/onboard/components/OnboardingModal.tsx +121 -267
  222. package/frontend/src/features/onboard/components/ShowPasswordButton.tsx +21 -0
  223. package/frontend/src/features/onboard/components/index.ts +9 -4
  224. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +1 -1
  225. package/frontend/src/features/onboard/components/mcp/QoderDeeplinkGenerator.tsx +36 -0
  226. package/frontend/src/features/onboard/components/mcp/helpers.tsx +123 -98
  227. package/frontend/src/features/onboard/components/mcp/index.ts +4 -3
  228. package/frontend/src/features/onboard/index.ts +17 -13
  229. package/frontend/src/features/settings/pages/SettingsPage.tsx +349 -0
  230. package/frontend/src/features/visualizer/components/AuthNode.tsx +4 -4
  231. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +21 -8
  232. package/frontend/src/features/visualizer/pages/VisualizerPage.tsx +10 -1
  233. package/frontend/src/index.css +249 -249
  234. package/frontend/src/lib/contexts/ModalContext.tsx +35 -0
  235. package/frontend/src/lib/hooks/useMetadata.ts +45 -1
  236. package/frontend/src/lib/hooks/useModal.tsx +2 -0
  237. package/frontend/src/lib/routing/AppRoutes.tsx +103 -99
  238. package/frontend/src/lib/services/metadata.service.ts +20 -3
  239. package/frontend/src/lib/utils/menuItems.ts +223 -207
  240. package/frontend/src/lib/utils/utils.ts +196 -196
  241. package/functions/server.ts +315 -315
  242. package/functions/worker-template.js +1 -1
  243. package/openapi/ai.yaml +115 -5
  244. package/openapi/auth.yaml +97 -17
  245. package/openapi/logs.yaml +0 -2
  246. package/openapi/metadata.yaml +0 -2
  247. package/openapi/records.yaml +21 -21
  248. package/openapi/tables.yaml +1 -2
  249. package/package.json +1 -1
  250. package/shared-schemas/package.json +1 -1
  251. package/shared-schemas/src/ai-api.schema.ts +251 -143
  252. package/shared-schemas/src/ai.schema.ts +63 -63
  253. package/shared-schemas/src/auth-api.schema.ts +34 -6
  254. package/shared-schemas/src/auth.schema.ts +17 -10
  255. package/shared-schemas/src/cloud-events.schema.ts +26 -0
  256. package/shared-schemas/src/deployments-api.schema.ts +55 -0
  257. package/shared-schemas/src/deployments.schema.ts +30 -0
  258. package/shared-schemas/src/docs.schema.ts +8 -2
  259. package/shared-schemas/src/email-api.schema.ts +30 -30
  260. package/shared-schemas/src/functions-api.schema.ts +13 -4
  261. package/shared-schemas/src/functions.schema.ts +1 -1
  262. package/shared-schemas/src/index.ts +22 -18
  263. package/shared-schemas/src/metadata.schema.ts +30 -4
  264. package/shared-schemas/src/secrets-api.schema.ts +44 -0
  265. package/shared-schemas/src/secrets.schema.ts +15 -0
  266. package/zeabur/README.md +13 -0
  267. package/zeabur/template.yml +20 -51
  268. package/backend/src/types/profile.ts +0 -55
  269. package/frontend/src/components/ProjectInfoModal.tsx +0 -128
@@ -10,11 +10,7 @@ import { successResponse } from '@/utils/response.js';
10
10
  import { AuthRequest, verifyAdmin, verifyToken } from '@/api/middlewares/auth.js';
11
11
  import oauthRouter from './oauth.routes.js';
12
12
  import { sendEmailOTPLimiter, verifyOTPLimiter } from '@/api/middlewares/rate-limiters.js';
13
- import {
14
- REFRESH_TOKEN_COOKIE_NAME,
15
- setAuthCookie,
16
- clearAuthCookie,
17
- } from '@/utils/cookies.js';
13
+ import { REFRESH_TOKEN_COOKIE_NAME, setAuthCookie, clearAuthCookie } from '@/utils/cookies.js';
18
14
  import {
19
15
  userIdSchema,
20
16
  createUserRequestSchema,
@@ -27,6 +23,7 @@ import {
27
23
  sendResetPasswordEmailRequestSchema,
28
24
  exchangeResetPasswordTokenRequestSchema,
29
25
  resetPasswordRequestSchema,
26
+ updateProfileRequestSchema,
30
27
  type CreateUserResponse,
31
28
  type CreateSessionResponse,
32
29
  type VerifyEmailResponse,
@@ -34,6 +31,7 @@ import {
34
31
  type ResetPasswordResponse,
35
32
  type CreateAdminSessionResponse,
36
33
  type GetCurrentSessionResponse,
34
+ type GetProfileResponse,
37
35
  type ListUsersResponse,
38
36
  type DeleteUsersResponse,
39
37
  type GetPublicAuthConfigResponse,
@@ -74,6 +72,60 @@ router.get('/public-config', async (req: Request, res: Response, next: NextFunct
74
72
  }
75
73
  });
76
74
 
75
+ // PATCH /api/auth/profiles/current - Update current user's profile (authenticated)
76
+ router.patch(
77
+ '/profiles/current',
78
+ verifyToken,
79
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
80
+ try {
81
+ if (!req.user) {
82
+ throw new AppError('User not authenticated', 401, ERROR_CODES.AUTH_INVALID_CREDENTIALS);
83
+ }
84
+
85
+ const validationResult = updateProfileRequestSchema.safeParse(req.body);
86
+ if (!validationResult.success) {
87
+ throw new AppError(
88
+ validationResult.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', '),
89
+ 400,
90
+ ERROR_CODES.INVALID_INPUT
91
+ );
92
+ }
93
+
94
+ const { profile } = validationResult.data;
95
+ const result = await authService.updateProfile(req.user.id, profile);
96
+
97
+ const response: GetProfileResponse = result;
98
+
99
+ successResponse(res, response);
100
+ } catch (error) {
101
+ next(error);
102
+ }
103
+ }
104
+ );
105
+
106
+ // GET /api/auth/profiles/:userId - Get user profile by ID (public endpoint)
107
+ router.get('/profiles/:userId', async (req: Request, res: Response, next: NextFunction) => {
108
+ try {
109
+ const userIdValidation = userIdSchema.safeParse(req.params.userId);
110
+ if (!userIdValidation.success) {
111
+ throw new AppError('Invalid user ID format', 400, ERROR_CODES.INVALID_INPUT);
112
+ }
113
+
114
+ const userId = userIdValidation.data;
115
+ const userProfile = await authService.getProfileById(userId);
116
+
117
+ if (!userProfile) {
118
+ throw new AppError('User not found', 404, ERROR_CODES.NOT_FOUND);
119
+ }
120
+
121
+ const response: GetProfileResponse = userProfile;
122
+
123
+ successResponse(res, response);
124
+ } catch (error) {
125
+ next(error);
126
+ }
127
+ });
128
+
77
129
  // Email Authentication Configuration Routes
78
130
  // GET /api/auth/config - Get authentication configurations (admin only)
79
131
  router.get('/config', verifyAdmin, async (req: AuthRequest, res: Response, next: NextFunction) => {
@@ -128,16 +180,15 @@ router.post('/users', async (req: Request, res: Response, next: NextFunction) =>
128
180
  );
129
181
  }
130
182
 
131
- const { email, password, name } = validationResult.data;
132
- const result: CreateUserResponse = await authService.register(email, password, name);
183
+ const { email, password, name, options } = validationResult.data;
184
+ const result: CreateUserResponse = await authService.register(email, password, name, options);
133
185
 
134
186
  // Set refresh token in httpOnly cookie and generate CSRF token
135
- let csrfToken: string | null = null;
136
187
  if (result.accessToken && result.user) {
137
188
  const tokenManager = TokenManager.getInstance();
138
189
  const refreshToken = tokenManager.generateRefreshToken(result.user.id);
139
- setAuthCookie(res, REFRESH_TOKEN_COOKIE_NAME, refreshToken);
140
- csrfToken = tokenManager.generateCsrfToken(refreshToken);
190
+ setAuthCookie(req, res, REFRESH_TOKEN_COOKIE_NAME, refreshToken);
191
+ result.csrfToken = tokenManager.generateCsrfToken(refreshToken);
141
192
  }
142
193
 
143
194
  const socket = SocketManager.getInstance();
@@ -148,7 +199,7 @@ router.post('/users', async (req: Request, res: Response, next: NextFunction) =>
148
199
  'system'
149
200
  );
150
201
 
151
- successResponse(res, { ...result, csrfToken });
202
+ successResponse(res, result);
152
203
  } catch (error) {
153
204
  next(error);
154
205
  }
@@ -172,10 +223,10 @@ router.post('/sessions', async (req: Request, res: Response, next: NextFunction)
172
223
  // Set refresh token in httpOnly cookie and generate CSRF token
173
224
  const tokenManager = TokenManager.getInstance();
174
225
  const refreshToken = tokenManager.generateRefreshToken(result.user.id);
175
- setAuthCookie(res, REFRESH_TOKEN_COOKIE_NAME, refreshToken);
176
- const csrfToken = tokenManager.generateCsrfToken(refreshToken);
226
+ setAuthCookie(req, res, REFRESH_TOKEN_COOKIE_NAME, refreshToken);
227
+ result.csrfToken = tokenManager.generateCsrfToken(refreshToken);
177
228
 
178
- successResponse(res, { ...result, csrfToken });
229
+ successResponse(res, result);
179
230
  } catch (error) {
180
231
  next(error);
181
232
  }
@@ -206,7 +257,7 @@ router.post('/refresh', async (req: Request, res: Response, next: NextFunction)
206
257
 
207
258
  if (!user) {
208
259
  logger.warn('[Auth:Refresh] User not found for valid refresh token', { userId: payload.sub });
209
- clearAuthCookie(res, REFRESH_TOKEN_COOKIE_NAME);
260
+ clearAuthCookie(req, res, REFRESH_TOKEN_COOKIE_NAME);
210
261
  throw new AppError('User not found', 401, ERROR_CODES.AUTH_UNAUTHORIZED);
211
262
  }
212
263
 
@@ -220,7 +271,7 @@ router.post('/refresh', async (req: Request, res: Response, next: NextFunction)
220
271
  // Generate new refresh token (token rotation for security)
221
272
  const newRefreshToken = tokenManager.generateRefreshToken(user.id);
222
273
 
223
- setAuthCookie(res, REFRESH_TOKEN_COOKIE_NAME, newRefreshToken);
274
+ setAuthCookie(req, res, REFRESH_TOKEN_COOKIE_NAME, newRefreshToken);
224
275
  const newCsrfToken = tokenManager.generateCsrfToken(newRefreshToken);
225
276
 
226
277
  successResponse(res, {
@@ -230,15 +281,15 @@ router.post('/refresh', async (req: Request, res: Response, next: NextFunction)
230
281
  });
231
282
  } catch (error) {
232
283
  // Clear invalid cookie on error
233
- clearAuthCookie(res, REFRESH_TOKEN_COOKIE_NAME);
284
+ clearAuthCookie(req, res, REFRESH_TOKEN_COOKIE_NAME);
234
285
  next(error);
235
286
  }
236
287
  });
237
288
 
238
289
  // POST /api/auth/logout - Logout and clear refresh token cookie
239
- router.post('/logout', (_req: Request, res: Response, next: NextFunction) => {
290
+ router.post('/logout', (req: Request, res: Response, next: NextFunction) => {
240
291
  try {
241
- clearAuthCookie(res, REFRESH_TOKEN_COOKIE_NAME);
292
+ clearAuthCookie(req, res, REFRESH_TOKEN_COOKIE_NAME);
242
293
 
243
294
  successResponse(res, {
244
295
  success: true,
@@ -307,18 +358,19 @@ router.post('/admin/sessions', (req: Request, res: Response, next: NextFunction)
307
358
  router.get(
308
359
  '/sessions/current',
309
360
  verifyToken,
310
- (req: AuthRequest, res: Response, next: NextFunction) => {
361
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
311
362
  try {
312
363
  if (!req.user) {
313
364
  throw new AppError('User not authenticated', 401, ERROR_CODES.AUTH_INVALID_CREDENTIALS);
314
365
  }
315
366
 
367
+ const user = await authService.getUserSchemaById(req.user.id);
368
+ if (!user) {
369
+ throw new AppError('User not found', 401, ERROR_CODES.AUTH_INVALID_CREDENTIALS);
370
+ }
371
+
316
372
  const response: GetCurrentSessionResponse = {
317
- user: {
318
- id: req.user.id,
319
- email: req.user.email,
320
- role: req.user.role as 'authenticated' | 'project_admin',
321
- },
373
+ user,
322
374
  };
323
375
 
324
376
  successResponse(res, response);
@@ -359,7 +411,7 @@ router.get('/users', verifyAdmin, async (req: Request, res: Response, next: Next
359
411
  }
360
412
  });
361
413
 
362
- // GET /api/auth/users/:id - Get specific user (admin only)
414
+ // GET /api/auth/users/:userId - Get specific user (admin only)
363
415
  router.get(
364
416
  '/users/:userId',
365
417
  verifyAdmin,
@@ -375,7 +427,7 @@ router.get(
375
427
  const user = await authService.getUserSchemaById(userId);
376
428
 
377
429
  if (!user) {
378
- throw new AppError('User not found', 404, ERROR_CODES.NOT_FOUND);
430
+ throw new AppError('User does not exist', 404, ERROR_CODES.NOT_FOUND);
379
431
  }
380
432
 
381
433
  successResponse(res, user);
@@ -458,7 +510,7 @@ router.post(
458
510
  );
459
511
  }
460
512
 
461
- const { email } = validationResult.data;
513
+ const { email, options } = validationResult.data;
462
514
 
463
515
  // Get auth config to determine verification method
464
516
  const authConfig = await authConfigService.getAuthConfig();
@@ -467,7 +519,8 @@ router.post(
467
519
  // Note: User enumeration is prevented at service layer
468
520
  // Service returns gracefully (no error) if user not found
469
521
  if (method === 'link') {
470
- await authService.sendVerificationEmailWithLink(email);
522
+ const redirectTo = authConfig.signInRedirectTo || options?.emailRedirectTo;
523
+ await authService.sendVerificationEmailWithLink(email, redirectTo);
471
524
  } else {
472
525
  await authService.sendVerificationEmailWithCode(email);
473
526
  }
@@ -536,9 +589,9 @@ router.post(
536
589
  // Set refresh token in httpOnly cookie and generate CSRF token
537
590
  const tokenManager = TokenManager.getInstance();
538
591
  const refreshToken = tokenManager.generateRefreshToken(result.user.id);
539
- setAuthCookie(res, REFRESH_TOKEN_COOKIE_NAME, refreshToken);
540
- const csrfToken = tokenManager.generateCsrfToken(refreshToken);
541
- successResponse(res, { ...result, csrfToken });
592
+ setAuthCookie(req, res, REFRESH_TOKEN_COOKIE_NAME, refreshToken);
593
+ result.csrfToken = tokenManager.generateCsrfToken(refreshToken);
594
+ successResponse(res, result);
542
595
  } catch (error) {
543
596
  next(error);
544
597
  }
@@ -1,6 +1,7 @@
1
1
  import { Router, Request, Response, NextFunction } from 'express';
2
2
  import { AuthService } from '@/services/auth/auth.service.js';
3
3
  import { OAuthConfigService } from '@/services/auth/oauth-config.service.js';
4
+ import { AuthConfigService } from '@/services/auth/auth-config.service.js';
4
5
  import { AuditService } from '@/services/logs/audit.service.js';
5
6
  import { TokenManager } from '@/infra/security/token.manager.js';
6
7
  import { AppError } from '@/api/middlewares/error.js';
@@ -20,6 +21,7 @@ import { isOAuthSharedKeysAvailable } from '@/utils/environment.js';
20
21
 
21
22
  const router = Router();
22
23
  const authService = AuthService.getInstance();
24
+ const authConfigService = AuthConfigService.getInstance();
23
25
  const oAuthConfigService = OAuthConfigService.getInstance();
24
26
  const auditService = AuditService.getInstance();
25
27
 
@@ -246,14 +248,17 @@ router.get('/:provider', async (req: Request, res: Response, next: NextFunction)
246
248
  }
247
249
 
248
250
  const validatedProvider = providerValidation.data;
251
+ const authConfig = await authConfigService.getAuthConfig();
249
252
 
250
- if (!redirect_uri) {
253
+ const redirectUri = authConfig.signInRedirectTo || redirect_uri;
254
+
255
+ if (!redirectUri) {
251
256
  throw new AppError('Redirect URI is required', 400, ERROR_CODES.INVALID_INPUT);
252
257
  }
253
258
 
254
259
  const jwtPayload = {
255
260
  provider: validatedProvider,
256
- redirectUri: redirect_uri ? (redirect_uri as string) : undefined,
261
+ redirectUri,
257
262
  createdAt: Date.now(),
258
263
  };
259
264
  const jwtSecret = validateJwtSecret();
@@ -346,7 +351,7 @@ router.get('/shared/callback/:state', async (req: Request, res: Response, next:
346
351
  // Set refresh token in httpOnly cookie and generate CSRF token
347
352
  const tokenManager = TokenManager.getInstance();
348
353
  const refreshToken = tokenManager.generateRefreshToken(result.user.id);
349
- setAuthCookie(res, REFRESH_TOKEN_COOKIE_NAME, refreshToken);
354
+ setAuthCookie(req, res, REFRESH_TOKEN_COOKIE_NAME, refreshToken);
350
355
  const csrfToken = tokenManager.generateCsrfToken(refreshToken);
351
356
 
352
357
  const params = new URLSearchParams();
@@ -354,7 +359,7 @@ router.get('/shared/callback/:state', async (req: Request, res: Response, next:
354
359
  params.set('access_token', result.accessToken);
355
360
  params.set('user_id', result.user.id);
356
361
  params.set('email', result.user.email);
357
- params.set('name', result.user.name);
362
+ params.set('name', String(result?.user?.profile?.name));
358
363
  params.set('csrf_token', csrfToken);
359
364
 
360
365
  res.redirect(`${redirectUri}?${params.toString()}`);
@@ -425,7 +430,7 @@ const handleOAuthCallback = async (req: Request, res: Response, next: NextFuncti
425
430
  // Set refresh token in httpOnly cookie and generate CSRF token
426
431
  const tokenManager = TokenManager.getInstance();
427
432
  const refreshToken = tokenManager.generateRefreshToken(result.user.id);
428
- setAuthCookie(res, REFRESH_TOKEN_COOKIE_NAME, refreshToken);
433
+ setAuthCookie(req, res, REFRESH_TOKEN_COOKIE_NAME, refreshToken);
429
434
  const csrfToken = tokenManager.generateCsrfToken(refreshToken);
430
435
 
431
436
  // Construct redirect URL with query parameters
@@ -434,7 +439,7 @@ const handleOAuthCallback = async (req: Request, res: Response, next: NextFuncti
434
439
  params.set('access_token', result.accessToken);
435
440
  params.set('user_id', result.user.id);
436
441
  params.set('email', result.user.email);
437
- params.set('name', result.user.name);
442
+ params.set('name', String(result?.user?.profile?.name));
438
443
  params.set('csrf_token', csrfToken);
439
444
 
440
445
  const finalRedirectUri = `${redirectUri}?${params.toString()}`;
@@ -1,6 +1,7 @@
1
1
  import { Router, Response, NextFunction } from 'express';
2
2
  import { databaseTablesRouter } from './tables.routes.js';
3
3
  import { databaseRecordsRouter } from './records.routes.js';
4
+ import { databaseRpcRouter } from './rpc.routes.js';
4
5
  import databaseAdvanceRouter from './advance.routes.js';
5
6
  import { DatabaseService } from '@/services/database/database.service.js';
6
7
  import { verifyAdmin, AuthRequest } from '@/api/middlewares/auth.js';
@@ -13,6 +14,7 @@ const databaseService = DatabaseService.getInstance();
13
14
  // Mount database sub-routes
14
15
  router.use('/tables', databaseTablesRouter);
15
16
  router.use('/records', databaseRecordsRouter);
17
+ router.use('/rpc', databaseRpcRouter);
16
18
  router.use('/advance', databaseAdvanceRouter);
17
19
 
18
20
  /**
@@ -1,80 +1,41 @@
1
1
  import { Router, Response, NextFunction } from 'express';
2
2
  import axios from 'axios';
3
- import http from 'http';
4
- import https from 'https';
5
3
  import { AuthRequest, extractApiKey } from '@/api/middlewares/auth.js';
6
4
  import { DatabaseManager } from '@/infra/database/database.manager.js';
7
- import { TokenManager } from '@/infra/security/token.manager.js';
8
5
  import { AppError } from '@/api/middlewares/error.js';
9
6
  import { ERROR_CODES } from '@/types/error-constants.js';
10
7
  import { validateTableName } from '@/utils/validations.js';
11
8
  import { DatabaseRecord } from '@/types/database.js';
12
9
  import { successResponse } from '@/utils/response.js';
13
- import logger from '@/utils/logger.js';
14
- import { SecretService } from '@/services/secrets/secret.service.js';
15
10
  import { SocketManager } from '@/infra/socket/socket.manager.js';
16
11
  import { DataUpdateResourceType, ServerEvents } from '@/types/socket.js';
17
12
  import { DatabaseResourceUpdate } from '@/utils/sql-parser.js';
13
+ import { PostgrestProxyService } from '@/services/database/postgrest-proxy.service.js';
18
14
 
19
15
  const router = Router();
20
- const secretService = SecretService.getInstance();
21
- const postgrestUrl = process.env.POSTGREST_BASE_URL || 'http://localhost:5430';
16
+ const proxyService = PostgrestProxyService.getInstance();
22
17
 
23
- // Create a dedicated HTTP agent with connection pooling for PostgREST
24
- // Optimized connection pool for Docker network communication
25
- const httpAgent = new http.Agent({
26
- keepAlive: true,
27
- keepAliveMsecs: 5000, // Shorter for Docker network
28
- maxSockets: 20, // Reduced for stability
29
- maxFreeSockets: 5,
30
- timeout: 10000, // Match axios timeout
31
- });
32
-
33
- const httpsAgent = new https.Agent({
34
- keepAlive: true,
35
- keepAliveMsecs: 5000,
36
- maxSockets: 20,
37
- maxFreeSockets: 5,
38
- timeout: 10000,
39
- });
40
-
41
- // Create axios instance with optimized configuration for PostgREST
42
- const postgrestAxios = axios.create({
43
- httpAgent,
44
- httpsAgent,
45
- timeout: 10000, // Increased timeout for stability
46
- maxRedirects: 0,
47
- // Additional connection stability options
48
- headers: {
49
- Connection: 'keep-alive',
50
- 'Keep-Alive': 'timeout=5, max=10',
51
- },
52
- });
53
-
54
- // Generate admin token once and reuse
55
- // If user request with api key, this token should be added automatically.
56
- const tokenManager = TokenManager.getInstance();
57
- const adminToken = tokenManager.generateAdminToken();
58
-
59
- // anonymous users can access the database, postgREST does not require authentication, however we seed to unwrap session token for better auth, thus
60
- // we need to verify user token below.
61
- // router.use(verifyUserOrApiKey);
18
+ /**
19
+ * Helper to handle PostgREST proxy errors
20
+ */
21
+ function handleProxyError(error: unknown, res: Response, next: NextFunction) {
22
+ if (axios.isAxiosError(error) && error.response) {
23
+ res.status(error.response.status).json(error.response.data);
24
+ } else {
25
+ next(error);
26
+ }
27
+ }
62
28
 
63
29
  /**
64
- * Forward database requests to PostgREST
30
+ * Forward database table requests to PostgREST
65
31
  */
66
32
  const forwardToPostgrest = async (req: AuthRequest, res: Response, next: NextFunction) => {
67
33
  const { tableName } = req.params;
68
34
  const wildcardPath = req.params[0] || '';
69
-
70
- // Build the target URL early so it's available in error handling
71
- const targetPath = wildcardPath ? `/${tableName}/${wildcardPath}` : `/${tableName}`;
72
- const targetUrl = `${postgrestUrl}${targetPath}`;
35
+ const path = wildcardPath ? `/${tableName}/${wildcardPath}` : `/${tableName}`;
73
36
 
74
37
  try {
75
- // Validate table name with operation type
76
- const method = req.method.toUpperCase();
77
-
38
+ // Validate table name
78
39
  try {
79
40
  validateTableName(tableName);
80
41
  } catch (error) {
@@ -84,11 +45,14 @@ const forwardToPostgrest = async (req: AuthRequest, res: Response, next: NextFun
84
45
  throw new AppError('Invalid table name', 400, ERROR_CODES.INVALID_INPUT);
85
46
  }
86
47
 
87
- // Process request body for POST/PATCH/PUT operations
88
- if (['POST', 'PATCH', 'PUT'].includes(method) && req.body && typeof req.body === 'object') {
48
+ // Process request body for POST/PATCH/PUT (filter empty values based on column types)
49
+ const method = req.method.toUpperCase();
50
+ let body = req.body;
51
+
52
+ if (['POST', 'PATCH', 'PUT'].includes(method) && body && typeof body === 'object') {
89
53
  const columnTypeMap = await DatabaseManager.getColumnTypeMap(tableName);
90
- if (Array.isArray(req.body)) {
91
- req.body = req.body.map((item) => {
54
+ if (Array.isArray(body)) {
55
+ body = body.map((item) => {
92
56
  if (item && typeof item === 'object') {
93
57
  const filtered: DatabaseRecord = {};
94
58
  for (const key in item) {
@@ -102,7 +66,6 @@ const forwardToPostgrest = async (req: AuthRequest, res: Response, next: NextFun
102
66
  return item;
103
67
  });
104
68
  } else {
105
- const body = req.body as DatabaseRecord;
106
69
  for (const key in body) {
107
70
  if (columnTypeMap[key] === 'uuid' && body[key] === '') {
108
71
  delete body[key];
@@ -111,100 +74,31 @@ const forwardToPostgrest = async (req: AuthRequest, res: Response, next: NextFun
111
74
  }
112
75
  }
113
76
 
114
- // Forward the request
115
- const axiosConfig: {
116
- method: string;
117
- url: string;
118
- params: unknown;
119
- headers: Record<string, string | string[] | undefined>;
120
- data?: unknown;
121
- } = {
77
+ // Forward to PostgREST via service
78
+ const result = await proxyService.forward({
122
79
  method: req.method,
123
- url: targetUrl,
124
- params: req.query,
125
- headers: {
126
- ...req.headers,
127
- host: undefined, // Remove host header
128
- 'content-length': undefined, // Let axios calculate
129
- },
130
- };
131
-
132
- // Check for API key using shared logic
133
- const apiKey = extractApiKey(req);
134
-
135
- // If we have an API key, verify it and use admin token for PostgREST
136
- if (apiKey) {
137
- const isValid = await secretService.verifyApiKey(apiKey);
138
- if (isValid) {
139
- axiosConfig.headers.authorization = `Bearer ${adminToken}`;
140
- }
141
- }
142
-
143
- // Add body for methods that support it
144
- if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
145
- axiosConfig.data = req.body;
146
- }
147
-
148
- // Enhanced retry logic with improved error handling
149
- let response;
150
- let lastError;
151
- const maxRetries = 3; // Increased retries for connection resets
152
-
153
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
154
- try {
155
- response = await postgrestAxios(axiosConfig);
156
- break; // Success, exit retry loop
157
- } catch (error) {
158
- lastError = error;
159
-
160
- // Retry on network errors (ECONNRESET, ECONNREFUSED, timeout) but not HTTP errors
161
- const shouldRetry = axios.isAxiosError(error) && !error.response && attempt < maxRetries;
162
-
163
- if (shouldRetry) {
164
- logger.warn(`PostgREST request failed, retrying (attempt ${attempt}/${maxRetries})`, {
165
- url: targetUrl,
166
- errorCode: error.code,
167
- message: error.message,
168
- });
169
-
170
- // Enhanced exponential backoff: 200ms, 500ms, 1000ms
171
- const backoffDelay = Math.min(200 * Math.pow(2.5, attempt - 1), 1000);
172
- await new Promise((resolve) => setTimeout(resolve, backoffDelay));
173
- } else {
174
- throw error; // Don't retry on HTTP errors or last attempt
175
- }
176
- }
177
- }
178
-
179
- if (!response) {
180
- throw lastError || new Error('Failed to get response from PostgREST');
181
- }
80
+ path,
81
+ query: req.query as Record<string, unknown>,
82
+ headers: req.headers as Record<string, string | string[] | undefined>,
83
+ body: ['POST', 'PUT', 'PATCH'].includes(req.method) ? body : undefined,
84
+ apiKey: extractApiKey(req) ?? undefined,
85
+ });
182
86
 
183
87
  // Forward response headers
184
- Object.entries(response.headers).forEach(([key, value]) => {
185
- const keyLower = key.toLowerCase();
186
- if (
187
- keyLower !== 'content-length' &&
188
- keyLower !== 'transfer-encoding' &&
189
- keyLower !== 'connection' &&
190
- keyLower !== 'content-encoding'
191
- ) {
192
- res.setHeader(key, value);
193
- }
194
- });
88
+ const headers = PostgrestProxyService.filterHeaders(result.headers);
89
+ Object.entries(headers).forEach(([key, value]) => res.setHeader(key, value));
195
90
 
196
91
  // Handle empty responses
197
- let responseData = response.data;
92
+ let responseData = result.data;
198
93
  if (
199
- response.data === undefined ||
200
- (typeof response.data === 'string' && response.data.trim() === '')
94
+ result.data === undefined ||
95
+ (typeof result.data === 'string' && result.data.trim() === '')
201
96
  ) {
202
97
  responseData = [];
203
98
  }
204
99
 
205
- // Only send socket events for mutations (POST, DELETE)
206
- const mutationMethods = ['POST', 'DELETE'];
207
- if (mutationMethods.includes(req.method.toUpperCase())) {
100
+ // Broadcast socket events for mutations
101
+ if (['POST', 'DELETE'].includes(method)) {
208
102
  const socket = SocketManager.getInstance();
209
103
  socket.broadcastToRoom(
210
104
  'role:project_admin',
@@ -217,39 +111,9 @@ const forwardToPostgrest = async (req: AuthRequest, res: Response, next: NextFun
217
111
  );
218
112
  }
219
113
 
220
- successResponse(res, responseData, response.status);
114
+ successResponse(res, responseData, result.status);
221
115
  } catch (error) {
222
- if (axios.isAxiosError(error)) {
223
- // Log more detailed error information
224
- logger.error('PostgREST request failed', {
225
- url: targetUrl,
226
- method: req.method,
227
- error: {
228
- code: error.code,
229
- message: error.message,
230
- response: error.response?.data,
231
- responseStatus: error.response?.status,
232
- },
233
- });
234
-
235
- // Forward PostgREST errors
236
- if (error.response) {
237
- res.status(error.response.status).json(error.response.data);
238
- } else {
239
- // Network error - connection refused, DNS failure, etc.
240
- const errorMessage =
241
- error.code === 'ECONNREFUSED'
242
- ? 'PostgREST connection refused'
243
- : error.code === 'ENOTFOUND'
244
- ? 'PostgREST service not found'
245
- : 'Database service unavailable';
246
-
247
- next(new AppError(errorMessage, 503, ERROR_CODES.INTERNAL_ERROR));
248
- }
249
- } else {
250
- logger.error('Unexpected error in database route', { error });
251
- next(error);
252
- }
116
+ handleProxyError(error, res, next);
253
117
  }
254
118
  };
255
119
 
@@ -0,0 +1,69 @@
1
+ import { Router, Response, NextFunction } from 'express';
2
+ import axios from 'axios';
3
+ import { AuthRequest, extractApiKey } from '@/api/middlewares/auth.js';
4
+ import { AppError } from '@/api/middlewares/error.js';
5
+ import { ERROR_CODES } from '@/types/error-constants.js';
6
+ import { validateFunctionName } from '@/utils/validations.js';
7
+ import { successResponse } from '@/utils/response.js';
8
+ import { PostgrestProxyService } from '@/services/database/postgrest-proxy.service.js';
9
+
10
+ const router = Router();
11
+ const proxyService = PostgrestProxyService.getInstance();
12
+
13
+ /**
14
+ * Helper to handle PostgREST proxy errors
15
+ */
16
+ function handleProxyError(error: unknown, res: Response, next: NextFunction) {
17
+ if (axios.isAxiosError(error) && error.response) {
18
+ res.status(error.response.status).json(error.response.data);
19
+ } else {
20
+ next(error);
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Forward RPC calls to PostgREST
26
+ */
27
+ const forwardRpcToPostgrest = async (req: AuthRequest, res: Response, next: NextFunction) => {
28
+ const { functionName } = req.params;
29
+
30
+ try {
31
+ // Validate function name
32
+ try {
33
+ validateFunctionName(functionName);
34
+ } catch (error) {
35
+ if (error instanceof AppError) {
36
+ throw error;
37
+ }
38
+ throw new AppError(`Invalid function name: ${functionName}`, 400, ERROR_CODES.INVALID_INPUT);
39
+ }
40
+
41
+ const result = await proxyService.forward({
42
+ method: req.method,
43
+ path: `/rpc/${functionName}`,
44
+ query: req.query as Record<string, unknown>,
45
+ headers: req.headers as Record<string, string | string[] | undefined>,
46
+ body: req.body,
47
+ apiKey: extractApiKey(req) ?? undefined,
48
+ });
49
+
50
+ const headers = PostgrestProxyService.filterHeaders(result.headers);
51
+ Object.entries(headers).forEach(([key, value]) => res.setHeader(key, value));
52
+
53
+ let responseData = result.data;
54
+ if (
55
+ result.data === undefined ||
56
+ (typeof result.data === 'string' && result.data.trim() === '')
57
+ ) {
58
+ responseData = null;
59
+ }
60
+
61
+ successResponse(res, responseData, result.status);
62
+ } catch (error) {
63
+ handleProxyError(error, res, next);
64
+ }
65
+ };
66
+
67
+ router.all('/:functionName', forwardRpcToPostgrest);
68
+
69
+ export { router as databaseRpcRouter };