insforge 0.3.2 → 1.2.10

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 (507) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.cursor/rules/cursor-rules.mdc +94 -0
  3. package/.dockerignore +3 -0
  4. package/.env.example +33 -4
  5. package/.github/ISSUE_TEMPLATE/bug_report.yml +13 -60
  6. package/.github/ISSUE_TEMPLATE/config.yml +2 -2
  7. package/.github/ISSUE_TEMPLATE/feature_request.yml +10 -63
  8. package/.github/PULL_REQUEST_TEMPLATE.md +7 -0
  9. package/.github/workflows/build-image.yml +2 -1
  10. package/.github/workflows/e2e.yml +63 -0
  11. package/CHANGELOG.md +41 -0
  12. package/CLAUDE_PLUGIN.md +104 -0
  13. package/CODE_OF_CONDUCT.md +128 -0
  14. package/CONTRIBUTING.md +1 -1
  15. package/Dockerfile +4 -1
  16. package/README.md +66 -18
  17. package/assets/mcpInstallv2.png +0 -0
  18. package/assets/sampleResponse.png +0 -0
  19. package/auth/index.html +13 -0
  20. package/auth/package.json +28 -0
  21. package/auth/public/favicon.ico +0 -0
  22. package/auth/src/App.tsx +33 -0
  23. package/auth/src/components/ErrorCard.tsx +37 -0
  24. package/auth/src/components/Layout.tsx +13 -0
  25. package/auth/src/index.css +19 -0
  26. package/auth/src/lib/broadcastService.ts +115 -0
  27. package/auth/src/lib/utils.ts +11 -0
  28. package/auth/src/main.tsx +22 -0
  29. package/auth/src/pages/ForgotPasswordPage.tsx +11 -0
  30. package/auth/src/pages/ResetPasswordPage.tsx +11 -0
  31. package/auth/src/pages/SignInPage.tsx +57 -0
  32. package/auth/src/pages/SignUpPage.tsx +57 -0
  33. package/auth/src/pages/VerifyEmailPage.tsx +20 -0
  34. package/auth/src/vite-env.d.ts +10 -0
  35. package/auth/tsconfig.json +32 -0
  36. package/auth/tsconfig.node.json +11 -0
  37. package/auth/vite.config.ts +25 -0
  38. package/backend/package.json +9 -9
  39. package/backend/src/api/{middleware → middlewares}/auth.ts +8 -9
  40. package/backend/src/api/middlewares/rate-limiters.ts +127 -0
  41. package/backend/src/api/routes/{ai.ts → ai/index.routes.ts} +20 -24
  42. package/backend/src/api/routes/auth/index.routes.ts +570 -0
  43. package/backend/src/api/routes/auth/oauth.routes.ts +448 -0
  44. package/backend/src/api/routes/{database.advance.ts → database/advance.routes.ts} +107 -65
  45. package/backend/src/api/routes/database/index.routes.ts +13 -0
  46. package/backend/src/api/routes/{database.records.ts → database/records.routes.ts} +22 -8
  47. package/backend/src/api/routes/{database.tables.ts → database/tables.routes.ts} +20 -23
  48. package/backend/src/api/routes/docs/index.routes.ts +76 -0
  49. package/backend/src/api/routes/functions/index.routes.ts +188 -0
  50. package/backend/src/api/routes/{logs.ts → logs/index.routes.ts} +25 -30
  51. package/backend/src/api/routes/{metadata.ts → metadata/index.routes.ts} +21 -31
  52. package/backend/src/api/routes/{secrets.ts → secrets/index.routes.ts} +27 -22
  53. package/backend/src/api/routes/{storage.ts → storage/index.routes.ts} +34 -53
  54. package/backend/src/api/routes/usage/index.routes.ts +89 -0
  55. package/backend/src/infra/config/app.config.ts +51 -0
  56. package/backend/src/{core/database/manager.ts → infra/database/database.manager.ts} +76 -85
  57. package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -0
  58. package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +8 -0
  59. package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +60 -0
  60. package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -0
  61. package/backend/src/{core/secrets/encryption.ts → infra/security/encryption.manager.ts} +3 -2
  62. package/backend/src/infra/security/token.manager.ts +125 -0
  63. package/backend/src/{core/socket/socket.ts → infra/socket/socket.manager.ts} +15 -15
  64. package/backend/src/providers/ai/openrouter.provider.ts +377 -0
  65. package/backend/src/providers/email/base.provider.ts +41 -0
  66. package/backend/src/providers/email/cloud.provider.ts +187 -0
  67. package/backend/src/{core/logs/providers → providers/logs}/base.provider.ts +11 -11
  68. package/backend/src/{core/logs/providers → providers/logs}/cloudwatch.provider.ts +61 -38
  69. package/backend/src/providers/logs/local.provider.ts +185 -0
  70. package/backend/src/providers/oauth/base.provider.ts +29 -0
  71. package/backend/src/providers/oauth/discord.provider.ts +195 -0
  72. package/backend/src/providers/oauth/facebook.provider.ts +194 -0
  73. package/backend/src/providers/oauth/github.provider.ts +208 -0
  74. package/backend/src/providers/oauth/google.provider.ts +249 -0
  75. package/backend/src/providers/oauth/index.ts +7 -0
  76. package/backend/src/providers/oauth/linkedin.provider.ts +240 -0
  77. package/backend/src/providers/oauth/microsoft.provider.ts +169 -0
  78. package/backend/src/providers/oauth/x.provider.ts +202 -0
  79. package/backend/src/providers/storage/base.provider.ts +29 -0
  80. package/backend/src/providers/storage/local.provider.ts +103 -0
  81. package/backend/src/providers/storage/s3.provider.ts +313 -0
  82. package/backend/src/server.ts +70 -74
  83. package/backend/src/{core/ai/config.ts → services/ai/ai-config.service.ts} +19 -24
  84. package/backend/src/services/ai/ai-model.service.ts +60 -0
  85. package/backend/src/{core/ai/usage.ts → services/ai/ai-usage.service.ts} +28 -35
  86. package/backend/src/{core/ai/chat.ts → services/ai/chat-completion.service.ts} +37 -24
  87. package/backend/src/services/ai/helpers.ts +64 -0
  88. package/backend/src/{core/ai/image.ts → services/ai/image-generation.service.ts} +17 -19
  89. package/backend/src/services/ai/index.ts +13 -0
  90. package/backend/src/services/auth/auth-config.service.ts +250 -0
  91. package/backend/src/services/auth/auth-otp.service.ts +424 -0
  92. package/backend/src/services/auth/auth.service.ts +1136 -0
  93. package/backend/src/services/auth/index.ts +4 -0
  94. package/backend/src/{core/auth/oauth.ts → services/auth/oauth-config.service.ts} +106 -52
  95. package/backend/src/{core/database/advance.ts → services/database/database-advance.service.ts} +97 -131
  96. package/backend/src/services/database/database-table.service.ts +811 -0
  97. package/backend/src/services/email/email.service.ts +75 -0
  98. package/backend/src/{core/functions/functions.ts → services/functions/function.service.ts} +95 -88
  99. package/backend/src/{core/logs/audit.ts → services/logs/audit.service.ts} +92 -75
  100. package/backend/src/services/logs/log.service.ts +73 -0
  101. package/backend/src/{core/secrets/secrets.ts → services/secrets/secret.service.ts} +48 -66
  102. package/backend/src/services/storage/storage.service.ts +617 -0
  103. package/backend/src/services/usage/usage.service.ts +149 -0
  104. package/backend/src/types/auth.ts +66 -2
  105. package/backend/src/types/email.ts +8 -0
  106. package/backend/src/types/error-constants.ts +4 -0
  107. package/backend/src/types/logs.ts +0 -29
  108. package/backend/src/{core/socket/types.ts → types/socket.ts} +5 -6
  109. package/backend/src/utils/environment.ts +9 -3
  110. package/backend/src/utils/logger.ts +20 -2
  111. package/backend/src/utils/seed.ts +150 -57
  112. package/backend/src/utils/sql-parser.ts +1 -1
  113. package/backend/src/utils/utils.ts +114 -0
  114. package/backend/src/utils/validations.ts +40 -4
  115. package/backend/tests/local/test-ai-config.sh +129 -0
  116. package/backend/tests/local/test-ai-usage.sh +80 -0
  117. package/backend/tests/local/test-auth-router.sh +1 -1
  118. package/backend/tests/local/test-e2e.sh +1 -1
  119. package/backend/tests/local/test-functions.sh +123 -0
  120. package/backend/tests/local/test-logs.sh +132 -0
  121. package/backend/tests/local/test-public-bucket.sh +3 -3
  122. package/backend/tests/local/test-secrets.sh +14 -12
  123. package/backend/tests/local/test-traditional-rest.sh +2 -2
  124. package/backend/tests/manual/test-rawsql-modes.sh +244 -0
  125. package/backend/tests/test-config.sh +37 -1
  126. package/backend/tests/unit/cloud-token.test.ts +48 -0
  127. package/backend/tests/unit/constant.test.ts +8 -0
  128. package/backend/tests/unit/email.test.ts +372 -0
  129. package/backend/tests/unit/environment.test.ts +59 -0
  130. package/backend/tests/unit/helpers.test.ts +63 -0
  131. package/backend/tests/unit/logger.test.ts +22 -0
  132. package/backend/tests/unit/rate-limit.test.ts +154 -0
  133. package/backend/tests/unit/response.test.ts +58 -0
  134. package/backend/tests/unit/sql-parser.test.ts +74 -0
  135. package/backend/tests/unit/uuid.test.ts +21 -0
  136. package/backend/tests/unit/validations.test.ts +80 -0
  137. package/backend/tsconfig.json +1 -1
  138. package/backend/vitest.config.ts +11 -0
  139. package/claude-plugin/.claude-plugin/plugin.json +24 -0
  140. package/claude-plugin/README.md +133 -0
  141. package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +270 -0
  142. package/docker-compose.prod.yml +60 -4
  143. package/docker-compose.yml +65 -4
  144. package/docker-init/db/db-init.sql +6 -34
  145. package/docker-init/logs/vector.yml +236 -0
  146. package/docs/README.md +44 -0
  147. package/docs/changelog.mdx +67 -0
  148. package/docs/core-concepts/ai/architecture.mdx +373 -0
  149. package/docs/core-concepts/ai/sdk.mdx +213 -0
  150. package/docs/core-concepts/authentication/architecture.mdx +278 -0
  151. package/docs/core-concepts/authentication/sdk.mdx +414 -0
  152. package/docs/core-concepts/authentication/ui-components/customization.mdx +529 -0
  153. package/docs/core-concepts/authentication/ui-components/nextjs.mdx +221 -0
  154. package/docs/core-concepts/authentication/ui-components/react-router.mdx +184 -0
  155. package/docs/core-concepts/authentication/ui-components/react.mdx +129 -0
  156. package/docs/core-concepts/database/architecture.mdx +256 -0
  157. package/docs/core-concepts/database/sdk.mdx +382 -0
  158. package/docs/core-concepts/functions/architecture.mdx +105 -0
  159. package/docs/core-concepts/functions/sdk.mdx +184 -0
  160. package/docs/core-concepts/storage/architecture.mdx +243 -0
  161. package/docs/core-concepts/storage/sdk.mdx +253 -0
  162. package/docs/deployment/README.md +94 -0
  163. package/docs/deployment/deploy-to-aws-ec2.md +565 -0
  164. package/docs/deployment/deploy-to-azure-virtual-machines.md +313 -0
  165. package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -0
  166. package/docs/deployment/deploy-to-render.md +441 -0
  167. package/docs/docs.json +210 -0
  168. package/docs/examples/framework-guides/nextjs.mdx +131 -0
  169. package/docs/examples/framework-guides/nuxt.mdx +165 -0
  170. package/docs/examples/framework-guides/react.mdx +165 -0
  171. package/docs/examples/framework-guides/svelte.mdx +153 -0
  172. package/docs/examples/framework-guides/vue.mdx +159 -0
  173. package/docs/examples/overview.mdx +67 -0
  174. package/docs/favicon.svg +19 -0
  175. package/docs/images/changelog/nov-2025/auth-components.webp +0 -0
  176. package/docs/images/changelog/nov-2025/database-metadata.webp +0 -0
  177. package/docs/images/changelog/nov-2025/quickstart-prompts.webp +0 -0
  178. package/docs/images/changelog/nov-2025/sql-editor.webp +0 -0
  179. package/docs/images/changelog/nov-2025/usage-page.webp +0 -0
  180. package/docs/images/changelog/october-2025/csv-upload.webp +0 -0
  181. package/docs/images/changelog/october-2025/logs-feature.webp +0 -0
  182. package/docs/images/changelog/october-2025/oauth-providers.webp +0 -0
  183. package/docs/images/checks-passed.png +0 -0
  184. package/docs/images/dashboard-connect-expanded.png +0 -0
  185. package/docs/images/dashboard-connect.png +0 -0
  186. package/docs/images/hero-dark.png +0 -0
  187. package/docs/images/hero-light.png +0 -0
  188. package/docs/images/icons/ai.svg +4 -0
  189. package/docs/images/icons/auth.svg +1 -0
  190. package/docs/images/icons/database.svg +1 -0
  191. package/docs/images/icons/function.svg +1 -0
  192. package/docs/images/icons/storage.svg +1 -0
  193. package/docs/images/logos/nextjs.svg +4 -0
  194. package/docs/images/logos/nuxt.svg +4 -0
  195. package/docs/images/logos/react.svg +5 -0
  196. package/docs/images/logos/svelte.svg +4 -0
  197. package/docs/images/logos/vue.svg +5 -0
  198. package/docs/images/mcp-install.png +0 -0
  199. package/docs/images/onboarding-mcp.png +0 -0
  200. package/docs/insforge-instructions-sdk.md +55 -374
  201. package/docs/introduction.mdx +45 -0
  202. package/docs/logo/dark.svg +22 -0
  203. package/docs/logo/light.svg +20 -0
  204. package/docs/partnership.mdx +647 -0
  205. package/docs/quickstart.mdx +83 -0
  206. package/docs/showcase/2048-arena.png +0 -0
  207. package/docs/showcase/framegen-cloud.png +0 -0
  208. package/docs/showcase/line-connect-race.png +0 -0
  209. package/docs/showcase/moment-vibe.png +0 -0
  210. package/docs/showcase/national-flags.png +0 -0
  211. package/docs/showcase/pokemon-vibe.png +0 -0
  212. package/docs/showcase/pure-browse-buy.png +0 -0
  213. package/docs/showcase.mdx +52 -0
  214. package/docs/snippets/sdk-installation.mdx +22 -0
  215. package/docs/snippets/service-icons.mdx +27 -0
  216. package/eslint.config.js +10 -3
  217. package/frontend/package.json +10 -4
  218. package/frontend/src/App.tsx +13 -82
  219. package/frontend/src/assets/icons/connected.svg +3 -0
  220. package/frontend/src/assets/icons/loader.svg +9 -0
  221. package/frontend/src/assets/logos/apple.svg +4 -0
  222. package/frontend/src/assets/logos/discord.svg +1 -1
  223. package/frontend/src/assets/logos/facebook.svg +3 -0
  224. package/frontend/src/assets/logos/instagram.svg +2 -0
  225. package/frontend/src/assets/logos/linkedin.svg +3 -0
  226. package/frontend/src/assets/logos/microsoft.svg +1 -0
  227. package/frontend/src/assets/logos/spotify.svg +17 -0
  228. package/frontend/src/assets/logos/tiktok.svg +6 -0
  229. package/frontend/src/assets/logos/x.svg +3 -0
  230. package/frontend/src/components/Checkbox.tsx +27 -29
  231. package/frontend/src/components/CodeBlock.tsx +55 -2
  232. package/frontend/src/components/CodeEditor.tsx +92 -0
  233. package/frontend/src/components/ConfirmDialog.tsx +1 -1
  234. package/frontend/src/components/ConnectCTA.tsx +38 -0
  235. package/frontend/src/components/CopyButton.tsx +52 -15
  236. package/frontend/src/components/ErrorState.tsx +1 -2
  237. package/frontend/src/components/FeatureSidebar.tsx +6 -6
  238. package/frontend/src/components/FeatureSidebarItem.tsx +2 -2
  239. package/frontend/src/components/JsonHighlight.tsx +21 -9
  240. package/frontend/src/components/ProjectInfoModal.tsx +128 -0
  241. package/frontend/src/components/PromptDialog.tsx +1 -4
  242. package/frontend/src/components/SearchInput.tsx +1 -2
  243. package/frontend/src/components/Stepper.tsx +53 -0
  244. package/frontend/src/components/ThemeToggle.tsx +3 -3
  245. package/frontend/src/components/datagrid/DataGrid.tsx +25 -32
  246. package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +1 -2
  247. package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +2 -4
  248. package/frontend/src/components/datagrid/index.ts +23 -0
  249. package/frontend/src/components/index.ts +23 -30
  250. package/frontend/src/components/layout/AppHeader.tsx +133 -92
  251. package/frontend/src/components/layout/AppSidebar.tsx +80 -170
  252. package/frontend/src/components/layout/Layout.tsx +12 -23
  253. package/frontend/src/components/layout/PrimaryMenu.tsx +187 -0
  254. package/frontend/src/components/layout/SecondaryMenu.tsx +70 -0
  255. package/frontend/src/components/layout/index.ts +5 -0
  256. package/frontend/src/components/radix/Tooltip.tsx +24 -13
  257. package/frontend/src/components/radix/index.ts +22 -0
  258. package/frontend/src/features/ai/components/AIConfigCard.tsx +129 -83
  259. package/frontend/src/features/ai/components/AIEmptyState.tsx +12 -7
  260. package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +101 -0
  261. package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -0
  262. package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -0
  263. package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -0
  264. package/frontend/src/features/ai/components/index.ts +6 -0
  265. package/frontend/src/features/ai/helpers.ts +57 -71
  266. package/frontend/src/features/ai/hooks/useAIConfigs.ts +39 -113
  267. package/frontend/src/features/ai/hooks/useAIUsage.ts +0 -2
  268. package/frontend/src/features/ai/page/AIPage.tsx +67 -79
  269. package/frontend/src/features/ai/services/ai.service.ts +5 -5
  270. package/frontend/src/features/auth/components/AuthPreview.tsx +96 -0
  271. package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +53 -30
  272. package/frontend/src/features/auth/components/UserFormDialog.tsx +13 -6
  273. package/frontend/src/features/auth/components/UsersDataGrid.tsx +44 -14
  274. package/frontend/src/features/auth/components/index.ts +5 -0
  275. package/frontend/src/features/auth/helpers.tsx +200 -0
  276. package/frontend/src/features/auth/hooks/useAnonToken.ts +30 -0
  277. package/frontend/src/features/auth/hooks/useAuthConfig.ts +48 -0
  278. package/frontend/src/features/auth/hooks/useOAuthConfig.ts +14 -10
  279. package/frontend/src/features/auth/hooks/useUsers.ts +43 -5
  280. package/frontend/src/features/auth/index.ts +3 -2
  281. package/frontend/src/features/auth/page/AuthMethodsPage.tsx +275 -0
  282. package/frontend/src/features/auth/page/ConfigurationPage.tsx +395 -0
  283. package/frontend/src/features/auth/page/UsersPage.tsx +285 -0
  284. package/frontend/src/features/auth/services/anonToken.service.ts +11 -0
  285. package/frontend/src/features/auth/services/config.service.ts +19 -0
  286. package/frontend/src/features/auth/services/{oauth.service.ts → oauth-config.service.ts} +4 -4
  287. package/frontend/src/features/auth/services/{auth.service.ts → user.service.ts} +7 -53
  288. package/frontend/src/features/dashboard/components/ConnectionSuccessBanner.tsx +35 -0
  289. package/frontend/src/features/dashboard/components/PromptCard.tsx +21 -0
  290. package/frontend/src/features/dashboard/components/PromptDialog.tsx +103 -0
  291. package/frontend/src/features/dashboard/components/StatsCard.tsx +50 -0
  292. package/frontend/src/features/dashboard/components/index.ts +4 -0
  293. package/frontend/src/features/dashboard/page/DashboardPage.tsx +187 -169
  294. package/frontend/src/features/dashboard/prompts/ai-chatbot.ts +13 -0
  295. package/frontend/src/features/dashboard/prompts/crm-system.ts +13 -0
  296. package/frontend/src/features/dashboard/prompts/ecommerce-platform.ts +12 -0
  297. package/frontend/src/features/dashboard/prompts/index.ts +31 -0
  298. package/frontend/src/features/dashboard/prompts/instagram-clone.ts +11 -0
  299. package/frontend/src/features/dashboard/prompts/notion-clone.ts +14 -0
  300. package/frontend/src/features/dashboard/prompts/reddit-clone.ts +12 -0
  301. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +48 -17
  302. package/frontend/src/features/database/components/ForeignKeyCell.tsx +15 -34
  303. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +19 -20
  304. package/frontend/src/features/database/components/LinkRecordModal.tsx +120 -125
  305. package/frontend/src/features/database/components/RecordFormDialog.tsx +22 -33
  306. package/frontend/src/features/database/components/RecordFormField.tsx +45 -47
  307. package/frontend/src/features/database/components/TableEmptyState.tsx +6 -5
  308. package/frontend/src/features/database/components/TableForm.tsx +28 -15
  309. package/frontend/src/features/database/components/TableFormColumn.tsx +2 -3
  310. package/frontend/src/features/database/components/TableSidebar.tsx +1 -1
  311. package/frontend/src/features/database/components/TablesEmptyState.tsx +48 -0
  312. package/frontend/src/features/database/components/TemplateCard.tsx +37 -0
  313. package/frontend/src/features/database/components/TemplatePreview.tsx +92 -0
  314. package/frontend/src/features/database/components/index.ts +19 -0
  315. package/frontend/src/features/database/constants.ts +28 -2
  316. package/frontend/src/features/database/contexts/SQLEditorContext.tsx +188 -0
  317. package/frontend/src/features/database/helpers.ts +2 -2
  318. package/frontend/src/features/database/hooks/useCSVImport.ts +29 -0
  319. package/frontend/src/features/database/hooks/useFullMetadata.ts +18 -0
  320. package/frontend/src/features/database/hooks/useRawSQL.ts +55 -0
  321. package/frontend/src/features/database/hooks/useRecords.ts +139 -0
  322. package/frontend/src/features/database/hooks/useTables.ts +131 -0
  323. package/frontend/src/features/database/index.ts +6 -1
  324. package/frontend/src/features/database/page/FunctionsPage.tsx +211 -0
  325. package/frontend/src/features/database/page/IndexesPage.tsx +240 -0
  326. package/frontend/src/features/database/page/PoliciesPage.tsx +248 -0
  327. package/frontend/src/features/database/page/SQLEditorPage.tsx +382 -0
  328. package/frontend/src/features/database/page/{DatabasePage.tsx → TablesPage.tsx} +186 -185
  329. package/frontend/src/features/database/page/TemplatesPage.tsx +39 -0
  330. package/frontend/src/features/database/page/TriggersPage.tsx +242 -0
  331. package/frontend/src/features/database/services/advance.service.ts +66 -0
  332. package/frontend/src/features/database/services/{database.service.ts → record.service.ts} +67 -64
  333. package/frontend/src/features/database/services/table.service.ts +64 -0
  334. package/frontend/src/features/database/templates/ai-chatbot.ts +402 -0
  335. package/frontend/src/features/database/templates/crm-system.ts +528 -0
  336. package/frontend/src/features/database/templates/ecommerce-platform.ts +553 -0
  337. package/frontend/src/features/database/templates/index.ts +34 -0
  338. package/frontend/src/features/database/templates/instagram-clone.ts +222 -0
  339. package/frontend/src/features/database/templates/notion-clone.ts +483 -0
  340. package/frontend/src/features/database/templates/reddit-clone.ts +526 -0
  341. package/frontend/src/features/functions/components/FunctionRow.tsx +2 -1
  342. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +1 -1
  343. package/frontend/src/features/functions/components/SecretRow.tsx +1 -1
  344. package/frontend/src/features/functions/components/index.ts +5 -0
  345. package/frontend/src/features/functions/hooks/useFunctions.ts +4 -4
  346. package/frontend/src/features/{secrets → functions}/hooks/useSecrets.ts +5 -5
  347. package/frontend/src/features/functions/page/FunctionsPage.tsx +160 -17
  348. package/frontend/src/features/functions/{components/SecretsContent.tsx → page/SecretsPage.tsx} +8 -12
  349. package/frontend/src/features/functions/services/{functions.service.ts → function.service.ts} +2 -2
  350. package/frontend/src/features/{secrets/services/secrets.service.ts → functions/services/secret.service.ts} +2 -2
  351. package/frontend/src/features/login/hooks/usePartnerOrigin.ts +27 -0
  352. package/frontend/src/features/login/page/CloudLoginPage.tsx +79 -54
  353. package/frontend/src/features/login/page/LoginPage.tsx +16 -23
  354. package/frontend/src/features/login/services/partnership.service.ts +65 -0
  355. package/frontend/src/features/logs/components/LogsDataGrid.tsx +89 -0
  356. package/frontend/src/features/logs/components/SeverityBadge.tsx +18 -0
  357. package/frontend/src/features/logs/components/index.ts +2 -0
  358. package/frontend/src/features/logs/helpers.ts +24 -0
  359. package/frontend/src/features/logs/hooks/useAuditLogs.ts +4 -4
  360. package/frontend/src/features/logs/hooks/useLogSources.ts +137 -0
  361. package/frontend/src/features/logs/hooks/useLogs.ts +163 -0
  362. package/frontend/src/features/logs/hooks/useMcpUsage.ts +181 -0
  363. package/frontend/src/features/logs/index.ts +8 -2
  364. package/frontend/src/features/logs/page/AuditsPage.tsx +91 -38
  365. package/frontend/src/features/logs/page/LogsPage.tsx +152 -0
  366. package/frontend/src/features/logs/page/MCPLogsPage.tsx +84 -0
  367. package/frontend/src/features/logs/services/audit.service.ts +63 -0
  368. package/frontend/src/features/logs/services/log.service.ts +15 -110
  369. package/frontend/src/features/logs/services/usage.service.ts +31 -0
  370. package/frontend/src/features/onboard/components/McpConnectionStatus.tsx +68 -0
  371. package/frontend/src/features/onboard/components/OnboardingModal.tsx +267 -0
  372. package/frontend/src/features/onboard/components/VideoDemoModal.tsx +38 -0
  373. package/frontend/src/features/onboard/components/index.ts +4 -0
  374. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +2 -2
  375. package/frontend/src/features/onboard/components/mcp/{mcp-helper.tsx → helpers.tsx} +8 -8
  376. package/frontend/src/features/onboard/components/mcp/index.ts +2 -3
  377. package/frontend/src/features/onboard/index.ts +13 -3
  378. package/frontend/src/features/storage/components/BucketEmptyState.tsx +9 -6
  379. package/frontend/src/features/storage/components/BucketFormDialog.tsx +25 -41
  380. package/frontend/src/features/storage/components/FilePreviewDialog.tsx +20 -8
  381. package/frontend/src/features/storage/components/StorageDataGrid.tsx +4 -3
  382. package/frontend/src/features/storage/components/StorageManager.tsx +23 -34
  383. package/frontend/src/features/storage/components/index.ts +12 -0
  384. package/frontend/src/features/storage/hooks/useStorage.ts +208 -0
  385. package/frontend/src/features/storage/page/StoragePage.tsx +41 -115
  386. package/frontend/src/features/storage/services/storage.service.ts +22 -1
  387. package/frontend/src/features/visualizer/components/AuthNode.tsx +72 -56
  388. package/frontend/src/features/visualizer/components/BucketNode.tsx +4 -4
  389. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +108 -80
  390. package/frontend/src/features/visualizer/components/TableNode.tsx +34 -41
  391. package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +12 -4
  392. package/frontend/src/features/visualizer/page/VisualizerPage.tsx +33 -29
  393. package/frontend/src/index.css +1 -0
  394. package/frontend/src/lib/analytics/posthog.tsx +27 -0
  395. package/frontend/src/lib/contexts/AuthContext.tsx +38 -31
  396. package/frontend/src/lib/contexts/SocketContext.tsx +5 -6
  397. package/frontend/src/{features/metadata → lib}/hooks/useMetadata.ts +1 -1
  398. package/frontend/src/lib/hooks/useToast.tsx +6 -2
  399. package/frontend/src/lib/routing/AppRoutes.tsx +84 -0
  400. package/frontend/src/lib/routing/RequireAuth.tsx +27 -0
  401. package/frontend/src/lib/utils/cloudMessaging.ts +20 -0
  402. package/frontend/src/lib/utils/menuItems.ts +183 -0
  403. package/frontend/src/lib/utils/{validation-schemas.ts → schemaValidations.ts} +10 -5
  404. package/frontend/src/lib/utils/utils.ts +19 -1
  405. package/frontend/src/vite-env.d.ts +1 -0
  406. package/frontend/vite.config.ts +5 -3
  407. package/functions/server.ts +28 -3
  408. package/functions/worker-template.js +15 -4
  409. package/i18n/README.ar.md +130 -0
  410. package/i18n/README.de.md +130 -0
  411. package/i18n/README.es.md +154 -0
  412. package/i18n/README.fr.md +134 -0
  413. package/i18n/README.hi.md +129 -0
  414. package/i18n/README.ja.md +174 -0
  415. package/i18n/README.ko.md +137 -0
  416. package/i18n/README.pt-BR.md +131 -0
  417. package/i18n/README.ru.md +129 -0
  418. package/i18n/README.zh-CN.md +133 -0
  419. package/openapi/ai.yaml +31 -4
  420. package/openapi/auth.yaml +827 -146
  421. package/package.json +16 -7
  422. package/shared-schemas/package.json +1 -1
  423. package/shared-schemas/src/ai-api.schema.ts +34 -58
  424. package/shared-schemas/src/ai.schema.ts +5 -0
  425. package/shared-schemas/src/auth-api.schema.ts +154 -8
  426. package/shared-schemas/src/auth.schema.ts +42 -6
  427. package/shared-schemas/src/cloud-events.schema.ts +57 -0
  428. package/shared-schemas/src/database-api.schema.ts +3 -3
  429. package/shared-schemas/src/database.schema.ts +1 -1
  430. package/shared-schemas/src/index.ts +1 -0
  431. package/shared-schemas/src/logs-api.schema.ts +7 -1
  432. package/shared-schemas/src/logs.schema.ts +26 -0
  433. package/shared-schemas/src/metadata.schema.ts +9 -4
  434. package/test-gemini.sh +35 -0
  435. package/test-usage-admin.sh +57 -0
  436. package/test-usage.sh +50 -0
  437. package/zeabur/README.md +13 -0
  438. package/zeabur/template.yml +1032 -0
  439. package/.github/workflows/deploy-aws.yml +0 -130
  440. package/backend/src/api/routes/agent.ts +0 -29
  441. package/backend/src/api/routes/auth.oauth.ts +0 -482
  442. package/backend/src/api/routes/auth.ts +0 -386
  443. package/backend/src/api/routes/docs.ts +0 -66
  444. package/backend/src/api/routes/functions.ts +0 -183
  445. package/backend/src/api/routes/openapi.ts +0 -82
  446. package/backend/src/api/routes/usage.ts +0 -96
  447. package/backend/src/core/ai/client.ts +0 -242
  448. package/backend/src/core/ai/model.ts +0 -117
  449. package/backend/src/core/auth/auth.ts +0 -781
  450. package/backend/src/core/database/table.ts +0 -772
  451. package/backend/src/core/documentation/agent.ts +0 -689
  452. package/backend/src/core/documentation/openapi.ts +0 -856
  453. package/backend/src/core/logs/analytics.ts +0 -76
  454. package/backend/src/core/logs/providers/localdb.provider.ts +0 -246
  455. package/backend/src/core/storage/storage.ts +0 -923
  456. package/backend/src/utils/cloud-token.ts +0 -39
  457. package/backend/src/utils/helpers.ts +0 -49
  458. package/backend/src/utils/uuid.ts +0 -9
  459. package/backend/tests/manual/test-better-auth.sh +0 -303
  460. package/docker-init/db/logs.sql +0 -9
  461. package/frontend/README.md +0 -112
  462. package/frontend/src/components/datagrid/index.tsx +0 -20
  463. package/frontend/src/components/layout/CloudLayout.tsx +0 -95
  464. package/frontend/src/features/ai/components/AIConfigDialog.tsx +0 -76
  465. package/frontend/src/features/ai/components/AIConfigForm.tsx +0 -222
  466. package/frontend/src/features/ai/components/fields/ModalityField.tsx +0 -87
  467. package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +0 -134
  468. package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +0 -33
  469. package/frontend/src/features/auth/components/AddOAuthDialog.tsx +0 -106
  470. package/frontend/src/features/auth/components/AuthMethodTab.tsx +0 -238
  471. package/frontend/src/features/auth/components/UsersTab.tsx +0 -114
  472. package/frontend/src/features/auth/page/AuthenticationPage.tsx +0 -169
  473. package/frontend/src/features/database/hooks/UseLinkModal.tsx +0 -78
  474. package/frontend/src/features/functions/components/FunctionViewer.tsx +0 -46
  475. package/frontend/src/features/functions/components/FunctionsContent.tsx +0 -88
  476. package/frontend/src/features/login/components/AuthErrorBoundary.tsx +0 -87
  477. package/frontend/src/features/login/components/PrivateRoute.tsx +0 -24
  478. package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +0 -313
  479. package/frontend/src/features/logs/components/LogsTable.tsx +0 -199
  480. package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +0 -530
  481. package/frontend/src/features/metadata/index.ts +0 -0
  482. package/frontend/src/features/metadata/page/MetadataPage.tsx +0 -136
  483. package/frontend/src/features/onboard/components/CompletionCard.tsx +0 -41
  484. package/frontend/src/features/onboard/components/OnboardButton.tsx +0 -84
  485. package/frontend/src/features/onboard/components/StepContent.tsx +0 -91
  486. package/frontend/src/features/onboard/components/TestConnectionStep.tsx +0 -53
  487. package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +0 -144
  488. package/frontend/src/features/onboard/page/OnBoardPage.tsx +0 -104
  489. package/frontend/src/features/onboard/types.ts +0 -8
  490. package/frontend/src/lib/contexts/OnboardStepContext.tsx +0 -68
  491. package/frontend/src/lib/hooks/useOnboardingCompletion.ts +0 -29
  492. /package/backend/src/api/{middleware → middlewares}/error.ts +0 -0
  493. /package/backend/src/api/{middleware → middlewares}/upload.ts +0 -0
  494. /package/backend/{migrations → src/infra/database/migrations}/000_create-base-tables.sql +0 -0
  495. /package/backend/{migrations → src/infra/database/migrations}/001_create-helper-functions.sql +0 -0
  496. /package/backend/{migrations → src/infra/database/migrations}/002_rename-auth-tables.sql +0 -0
  497. /package/backend/{migrations → src/infra/database/migrations}/003_create-users-table.sql +0 -0
  498. /package/backend/{migrations → src/infra/database/migrations}/004_add-reload-postgrest-func.sql +0 -0
  499. /package/backend/{migrations → src/infra/database/migrations}/005_enable-project-admin-modify-users.sql +0 -0
  500. /package/backend/{migrations → src/infra/database/migrations}/006_modify-ai-usage-table.sql +0 -0
  501. /package/backend/{migrations → src/infra/database/migrations}/007_drop-metadata-table.sql +0 -0
  502. /package/backend/{migrations → src/infra/database/migrations}/008_add-system-tables.sql +0 -0
  503. /package/backend/{migrations → src/infra/database/migrations}/009_add-function-secrets.sql +0 -0
  504. /package/backend/{migrations → src/infra/database/migrations}/010_modify-ai-config-modalities.sql +0 -0
  505. /package/backend/{migrations → src/infra/database/migrations}/011_refactor-secrets-table.sql +0 -0
  506. /package/backend/{migrations → src/infra/database/migrations}/012_add-storage-uploaded-by.sql +0 -0
  507. /package/frontend/src/{features/metadata → lib}/services/metadata.service.ts +0 -0
@@ -0,0 +1,372 @@
1
+ import { EmailService } from '../../src/core/email/email';
2
+ import { AppError } from '../../src/api/middleware/error';
3
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
4
+ import axios from 'axios';
5
+ import jwt from 'jsonwebtoken';
6
+
7
+ // Mock dependencies
8
+ vi.mock('axios');
9
+ vi.mock('jsonwebtoken');
10
+ vi.mock('../../src/utils/logger', () => ({
11
+ default: {
12
+ info: vi.fn(),
13
+ error: vi.fn(),
14
+ warn: vi.fn(),
15
+ },
16
+ }));
17
+ vi.mock('../../src/app.config', () => ({
18
+ config: {
19
+ app: {
20
+ jwtSecret: 'test-jwt-secret',
21
+ },
22
+ cloud: {
23
+ projectId: 'test-project-123',
24
+ apiHost: 'https://api.test.com',
25
+ },
26
+ },
27
+ }));
28
+
29
+ describe('EmailService', () => {
30
+ let emailService: EmailService;
31
+ const oldEnv = process.env;
32
+
33
+ beforeEach(async () => {
34
+ // Reset all mocks
35
+ vi.resetAllMocks();
36
+
37
+ // Mock config values
38
+ const { config } = await import('../../src/app.config');
39
+ config.cloud.projectId = 'test-project-123';
40
+ config.app.jwtSecret = 'test-jwt-secret';
41
+ config.cloud.apiHost = 'https://api.test.com';
42
+
43
+ // Get fresh instance
44
+ emailService = EmailService.getInstance();
45
+
46
+ // Default mock for jwt.sign
47
+ (jwt.sign as unknown as ReturnType<typeof vi.fn>).mockReturnValue('mocked-jwt-token');
48
+ });
49
+
50
+ afterEach(() => {
51
+ process.env = oldEnv;
52
+ });
53
+
54
+ describe('getInstance', () => {
55
+ it('returns singleton instance', () => {
56
+ const instance1 = EmailService.getInstance();
57
+ const instance2 = EmailService.getInstance();
58
+ expect(instance1).toBe(instance2);
59
+ });
60
+ });
61
+
62
+ describe('sendWithTemplate', () => {
63
+ it('successfully sends email with verification code template', async () => {
64
+ vi.mocked(axios.post).mockResolvedValue({
65
+ data: { success: true },
66
+ });
67
+
68
+ await emailService.sendWithTemplate(
69
+ 'user@example.com',
70
+ 'John Doe',
71
+ 'email-verification-code',
72
+ { token: '123456' }
73
+ );
74
+
75
+ expect(jwt.sign).toHaveBeenCalledWith({ sub: 'test-project-123' }, 'test-jwt-secret', {
76
+ expiresIn: '10m',
77
+ });
78
+
79
+ expect(axios.post).toHaveBeenCalledWith(
80
+ 'https://api.test.com/email/v1/test-project-123/send-with-template',
81
+ {
82
+ email: 'user@example.com',
83
+ name: 'John Doe',
84
+ template: 'email-verification-code',
85
+ variables: { token: '123456' },
86
+ },
87
+ {
88
+ headers: {
89
+ 'Content-Type': 'application/json',
90
+ sign: 'mocked-jwt-token',
91
+ },
92
+ timeout: 10000,
93
+ }
94
+ );
95
+ });
96
+
97
+ it('successfully sends email with verification link template', async () => {
98
+ vi.mocked(axios.post).mockResolvedValue({
99
+ data: { success: true },
100
+ });
101
+
102
+ await emailService.sendWithTemplate(
103
+ 'user@example.com',
104
+ 'John Doe',
105
+ 'email-verification-link',
106
+ { magic_link: 'https://example.com/verify?token=abc123' }
107
+ );
108
+
109
+ expect(axios.post).toHaveBeenCalledWith(
110
+ expect.any(String),
111
+ expect.objectContaining({
112
+ template: 'email-verification-link',
113
+ variables: { magic_link: 'https://example.com/verify?token=abc123' },
114
+ }),
115
+ expect.any(Object)
116
+ );
117
+ });
118
+
119
+ it('successfully sends email with reset-password code template', async () => {
120
+ vi.mocked(axios.post).mockResolvedValue({
121
+ data: { success: true },
122
+ });
123
+
124
+ await emailService.sendWithTemplate('user@example.com', 'Jane Smith', 'reset-password-code', {
125
+ token: 'reset123',
126
+ });
127
+
128
+ expect(axios.post).toHaveBeenCalledWith(
129
+ expect.any(String),
130
+ expect.objectContaining({
131
+ template: 'reset-password-code',
132
+ variables: { token: 'reset123' },
133
+ }),
134
+ expect.any(Object)
135
+ );
136
+ });
137
+
138
+ it('successfully sends email with reset-password link template', async () => {
139
+ vi.mocked(axios.post).mockResolvedValue({
140
+ data: { success: true },
141
+ });
142
+
143
+ await emailService.sendWithTemplate('user@example.com', 'Jane Smith', 'reset-password-link', {
144
+ magic_link: 'https://example.com/reset?token=xyz789',
145
+ });
146
+
147
+ expect(axios.post).toHaveBeenCalledWith(
148
+ expect.any(String),
149
+ expect.objectContaining({
150
+ template: 'reset-password-link',
151
+ variables: { magic_link: 'https://example.com/reset?token=xyz789' },
152
+ }),
153
+ expect.any(Object)
154
+ );
155
+ });
156
+
157
+ it('throws error if PROJECT_ID is not configured', async () => {
158
+ const { config } = await import('../../src/app.config');
159
+ config.cloud.projectId = 'local';
160
+
161
+ await expect(
162
+ emailService.sendWithTemplate('user@example.com', 'John', 'email-verification-code', {
163
+ token: '123456',
164
+ })
165
+ ).rejects.toThrow(AppError);
166
+
167
+ await expect(
168
+ emailService.sendWithTemplate('user@example.com', 'John', 'email-verification-code', {
169
+ token: '123456',
170
+ })
171
+ ).rejects.toThrow('PROJECT_ID is not configured');
172
+
173
+ // Reset for other tests
174
+ config.cloud.projectId = 'test-project-123';
175
+ });
176
+
177
+ it('throws error if JWT_SECRET is not configured', async () => {
178
+ const { config } = await import('../../src/app.config');
179
+ config.app.jwtSecret = '';
180
+
181
+ await expect(
182
+ emailService.sendWithTemplate('user@example.com', 'John', 'email-verification-code', {
183
+ token: '123456',
184
+ })
185
+ ).rejects.toThrow(AppError);
186
+
187
+ await expect(
188
+ emailService.sendWithTemplate('user@example.com', 'John', 'email-verification-code', {
189
+ token: '123456',
190
+ })
191
+ ).rejects.toThrow('JWT_SECRET is not configured');
192
+
193
+ // Reset for other tests
194
+ config.app.jwtSecret = 'test-jwt-secret';
195
+ });
196
+
197
+ it('throws error if required parameters are missing', async () => {
198
+ await expect(
199
+ emailService.sendWithTemplate('', 'John', 'email-verification-code')
200
+ ).rejects.toThrow('Missing required parameters');
201
+
202
+ await expect(
203
+ emailService.sendWithTemplate('user@example.com', '', 'email-verification-code')
204
+ ).rejects.toThrow('Missing required parameters');
205
+ });
206
+
207
+ it('throws error for invalid template type', async () => {
208
+ await expect(
209
+ emailService.sendWithTemplate(
210
+ 'user@example.com',
211
+ 'John',
212
+ '123456',
213
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
214
+ 'invalid-template' as any
215
+ )
216
+ ).rejects.toThrow('Invalid template type');
217
+ });
218
+
219
+ it('throws error if cloud service returns unsuccessful response', async () => {
220
+ vi.mocked(axios.post).mockResolvedValue({
221
+ data: { success: false },
222
+ });
223
+
224
+ await expect(
225
+ emailService.sendWithTemplate('user@example.com', 'John', 'email-verification-code', {
226
+ token: '123456',
227
+ })
228
+ ).rejects.toThrow('Email service returned unsuccessful response');
229
+ });
230
+
231
+ it('handles 401 authentication error', async () => {
232
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
233
+ const error = new Error('Request failed') as any;
234
+ error.isAxiosError = true;
235
+ error.response = {
236
+ status: 401,
237
+ data: { message: 'Unauthorized' },
238
+ };
239
+
240
+ vi.mocked(axios.post).mockRejectedValue(error);
241
+ vi.mocked(axios.isAxiosError).mockReturnValue(true);
242
+
243
+ await expect(
244
+ emailService.sendWithTemplate('user@example.com', 'John', 'email-verification-code', {
245
+ token: '123456',
246
+ })
247
+ ).rejects.toThrow('Authentication failed with cloud email service');
248
+ });
249
+
250
+ it('handles 403 forbidden error', async () => {
251
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
252
+ const error = new Error('Request failed') as any;
253
+ error.isAxiosError = true;
254
+ error.response = {
255
+ status: 403,
256
+ data: { message: 'Forbidden' },
257
+ };
258
+
259
+ vi.mocked(axios.post).mockRejectedValue(error);
260
+ vi.mocked(axios.isAxiosError).mockReturnValue(true);
261
+
262
+ await expect(
263
+ emailService.sendWithTemplate('user@example.com', 'John', 'email-verification-code', {
264
+ token: '123456',
265
+ })
266
+ ).rejects.toThrow('Authentication failed with cloud email service');
267
+ });
268
+
269
+ it('handles 429 rate limit error', async () => {
270
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
271
+ const error = new Error('Request failed') as any;
272
+ error.isAxiosError = true;
273
+ error.response = {
274
+ status: 429,
275
+ data: { message: 'Too many requests' },
276
+ };
277
+
278
+ vi.mocked(axios.post).mockRejectedValue(error);
279
+ vi.mocked(axios.isAxiosError).mockReturnValue(true);
280
+
281
+ await expect(
282
+ emailService.sendWithTemplate('user@example.com', 'John', 'email-verification-code', {
283
+ token: '123456',
284
+ })
285
+ ).rejects.toThrow('Email rate limit exceeded');
286
+ });
287
+
288
+ it('handles 400 bad request error', async () => {
289
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
290
+ const error = new Error('Request failed') as any;
291
+ error.isAxiosError = true;
292
+ error.response = {
293
+ status: 400,
294
+ data: { message: 'Invalid email format' },
295
+ };
296
+
297
+ vi.mocked(axios.post).mockRejectedValue(error);
298
+ vi.mocked(axios.isAxiosError).mockReturnValue(true);
299
+
300
+ await expect(
301
+ emailService.sendWithTemplate('user@example.com', 'John', 'email-verification-code', {
302
+ token: '123456',
303
+ })
304
+ ).rejects.toThrow('Invalid email request: Invalid email format');
305
+ });
306
+
307
+ it('handles generic axios error', async () => {
308
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
309
+ const error = new Error('Request failed') as any;
310
+ error.isAxiosError = true;
311
+ error.response = {
312
+ status: 500,
313
+ data: { message: 'Internal server error' },
314
+ };
315
+
316
+ vi.mocked(axios.post).mockRejectedValue(error);
317
+ vi.mocked(axios.isAxiosError).mockReturnValue(true);
318
+
319
+ await expect(
320
+ emailService.sendWithTemplate('user@example.com', 'John', 'email-verification-code', {
321
+ token: '123456',
322
+ })
323
+ ).rejects.toThrow('Failed to send email: Internal server error');
324
+ });
325
+
326
+ it('handles network error without response', async () => {
327
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
328
+ const error = new Error('Network error') as any;
329
+ error.isAxiosError = true;
330
+
331
+ vi.mocked(axios.post).mockRejectedValue(error);
332
+ vi.mocked(axios.isAxiosError).mockReturnValue(true);
333
+
334
+ await expect(
335
+ emailService.sendWithTemplate('user@example.com', 'John', 'email-verification-code', {
336
+ token: '123456',
337
+ })
338
+ ).rejects.toThrow('Failed to send email: Network error');
339
+ });
340
+
341
+ it('handles non-axios error', async () => {
342
+ vi.mocked(axios.post).mockRejectedValue(new Error('Unexpected error'));
343
+
344
+ await expect(
345
+ emailService.sendWithTemplate('user@example.com', 'John', 'email-verification-code', {
346
+ token: '123456',
347
+ })
348
+ ).rejects.toThrow('Failed to send email: Unexpected error');
349
+ });
350
+ });
351
+
352
+ describe('JWT token generation', () => {
353
+ it('generates JWT with correct payload and expiration', async () => {
354
+ vi.mocked(axios.post).mockResolvedValue({
355
+ data: { success: true },
356
+ });
357
+
358
+ await emailService.sendWithTemplate(
359
+ 'user@example.com',
360
+ 'John Doe',
361
+ 'email-verification-code',
362
+ {
363
+ token: '123456',
364
+ }
365
+ );
366
+
367
+ expect(jwt.sign).toHaveBeenCalledWith({ sub: 'test-project-123' }, 'test-jwt-secret', {
368
+ expiresIn: '10m',
369
+ });
370
+ });
371
+ });
372
+ });
@@ -0,0 +1,59 @@
1
+ import {
2
+ isCloudEnvironment,
3
+ isOAuthSharedKeysAvailable,
4
+ isDevelopment,
5
+ isProduction,
6
+ } from '../../src/utils/environment';
7
+ import { describe, it, expect, beforeEach, afterAll } from 'vitest';
8
+
9
+ describe('Environment utils', () => {
10
+ const OLD_ENV = process.env;
11
+
12
+ beforeEach(() => {
13
+ process.env = { ...OLD_ENV };
14
+ });
15
+
16
+ afterAll(() => {
17
+ process.env = OLD_ENV;
18
+ });
19
+
20
+ it('isCloudEnvironment returns true if AWS_INSTANCE_PROFILE_NAME is set', () => {
21
+ process.env.AWS_INSTANCE_PROFILE_NAME = 'my-profile';
22
+ expect(isCloudEnvironment()).toBe(true);
23
+ });
24
+
25
+ it('isCloudEnvironment returns false if AWS_INSTANCE_PROFILE_NAME is missing', () => {
26
+ delete process.env.AWS_INSTANCE_PROFILE_NAME;
27
+ expect(isCloudEnvironment()).toBe(false);
28
+ });
29
+
30
+ it('isOAuthSharedKeysAvailable returns same as isCloudEnvironment', () => {
31
+ process.env.AWS_INSTANCE_PROFILE_NAME = 'profile';
32
+ expect(isOAuthSharedKeysAvailable()).toBe(true);
33
+
34
+ delete process.env.AWS_INSTANCE_PROFILE_NAME;
35
+ expect(isOAuthSharedKeysAvailable()).toBe(false);
36
+ });
37
+
38
+ it('isDevelopment works correctly', () => {
39
+ process.env.NODE_ENV = 'development';
40
+ expect(isDevelopment()).toBe(true);
41
+
42
+ process.env.NODE_ENV = 'production';
43
+ expect(isDevelopment()).toBe(false);
44
+
45
+ delete process.env.NODE_ENV;
46
+ expect(isDevelopment()).toBe(true);
47
+ });
48
+
49
+ it('isProduction works correctly', () => {
50
+ process.env.NODE_ENV = 'production';
51
+ expect(isProduction()).toBe(true);
52
+
53
+ process.env.NODE_ENV = 'development';
54
+ expect(isProduction()).toBe(false);
55
+
56
+ delete process.env.NODE_ENV;
57
+ expect(isProduction()).toBe(false);
58
+ });
59
+ });
@@ -0,0 +1,63 @@
1
+ import { convertSqlTypeToColumnType } from '../../src/utils/utils';
2
+ import { ColumnType } from '@insforge/shared-schemas';
3
+ import { describe, it, expect } from 'vitest';
4
+ describe('convertSqlTypeToColumnType', () => {
5
+ it('converts UUID correctly', () => {
6
+ expect(convertSqlTypeToColumnType('uuid')).toBe(ColumnType.UUID);
7
+ expect(convertSqlTypeToColumnType('UUID')).toBe(ColumnType.UUID);
8
+ });
9
+
10
+ it('converts timestamp types correctly', () => {
11
+ expect(convertSqlTypeToColumnType('timestamptz')).toBe(ColumnType.DATETIME);
12
+ expect(convertSqlTypeToColumnType('timestamp with time zone')).toBe(ColumnType.DATETIME);
13
+ });
14
+
15
+ it('converts integer types correctly', () => {
16
+ const integers = [
17
+ 'integer',
18
+ 'bigint',
19
+ 'smallint',
20
+ 'int',
21
+ 'int2',
22
+ 'int4',
23
+ 'serial',
24
+ 'serial2',
25
+ 'serial4',
26
+ 'serial8',
27
+ 'smallserial',
28
+ 'bigserial',
29
+ ];
30
+ integers.forEach((type) => {
31
+ expect(convertSqlTypeToColumnType(type)).toBe(ColumnType.INTEGER);
32
+ });
33
+ });
34
+
35
+ it('converts float types correctly', () => {
36
+ const floats = ['double precision', 'real', 'numeric', 'float', 'float4', 'float8', 'decimal'];
37
+ floats.forEach((type) => {
38
+ expect(convertSqlTypeToColumnType(type)).toBe(ColumnType.FLOAT);
39
+ });
40
+ });
41
+
42
+ it('converts boolean types correctly', () => {
43
+ expect(convertSqlTypeToColumnType('boolean')).toBe(ColumnType.BOOLEAN);
44
+ expect(convertSqlTypeToColumnType('bool')).toBe(ColumnType.BOOLEAN);
45
+ });
46
+
47
+ it('converts JSON types correctly', () => {
48
+ expect(convertSqlTypeToColumnType('json')).toBe(ColumnType.JSON);
49
+ expect(convertSqlTypeToColumnType('jsonb')).toBe(ColumnType.JSON);
50
+ expect(convertSqlTypeToColumnType('array')).toBe(ColumnType.JSON);
51
+ });
52
+
53
+ it('converts string types correctly', () => {
54
+ const strings = ['text', 'varchar', 'char', 'character', 'character varying'];
55
+ strings.forEach((type) => {
56
+ expect(convertSqlTypeToColumnType(type)).toBe(ColumnType.STRING);
57
+ });
58
+ });
59
+
60
+ it('returns first 5 chars for unknown types', () => {
61
+ expect(convertSqlTypeToColumnType('customtype')).toBe('custo');
62
+ });
63
+ });
@@ -0,0 +1,22 @@
1
+ import { logger } from '../../src/utils/logger';
2
+ import { describe, it, expect, vi } from 'vitest';
3
+
4
+ describe('Logger', () => {
5
+ it('should initialize with correct log level from environment or default', () => {
6
+ expect(logger.level).toBe(process.env.LOG_LEVEL || 'info');
7
+ });
8
+
9
+ it('should log messages without throwing', () => {
10
+ expect(() => logger.info('Test info message')).not.toThrow();
11
+ expect(() => logger.warn('Test warn message')).not.toThrow();
12
+ expect(() => logger.error('Test error message')).not.toThrow();
13
+ });
14
+
15
+ it('should call logger.error when logging errors', () => {
16
+ const error = new Error('Test error');
17
+ const logSpy = vi.spyOn(logger, 'error').mockImplementation(() => logger);
18
+ logger.error(error);
19
+ expect(logSpy).toHaveBeenCalled();
20
+ logSpy.mockRestore();
21
+ });
22
+ });
@@ -0,0 +1,154 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { Request, Response } from 'express';
3
+ import { perEmailCooldown } from '../../src/api/middleware/rate-limiters';
4
+ import { AppError } from '../../src/api/middleware/error';
5
+
6
+ describe('Rate Limit Middleware', () => {
7
+ describe('perEmailCooldown', () => {
8
+ let req: Partial<Request>;
9
+ let res: Partial<Response>;
10
+ let next: ReturnType<typeof vi.fn>;
11
+
12
+ beforeEach(() => {
13
+ req = {
14
+ body: {},
15
+ };
16
+ res = {};
17
+ next = vi.fn();
18
+ });
19
+
20
+ it('allows first request for an email', () => {
21
+ req.body = { email: 'test@example.com' };
22
+ const middleware = perEmailCooldown(60000);
23
+
24
+ middleware(req as Request, res as Response, next);
25
+
26
+ expect(next).toHaveBeenCalledOnce();
27
+ expect(next).toHaveBeenCalledWith();
28
+ });
29
+
30
+ it('blocks second request within cooldown period', () => {
31
+ req.body = { email: 'test2@example.com' };
32
+ const middleware = perEmailCooldown(60000);
33
+
34
+ // First request should pass
35
+ middleware(req as Request, res as Response, next);
36
+ expect(next).toHaveBeenCalledOnce();
37
+
38
+ // Second request should be blocked
39
+ expect(() => {
40
+ middleware(req as Request, res as Response, next);
41
+ }).toThrow(AppError);
42
+
43
+ expect(() => {
44
+ middleware(req as Request, res as Response, next);
45
+ }).toThrow(/Please wait.*seconds before requesting another code/);
46
+ });
47
+
48
+ it('allows request after cooldown period expires', async () => {
49
+ req.body = { email: 'test3@example.com' };
50
+ const shortCooldown = 100; // 100ms cooldown
51
+ const middleware = perEmailCooldown(shortCooldown);
52
+
53
+ // First request
54
+ middleware(req as Request, res as Response, next);
55
+ expect(next).toHaveBeenCalledTimes(1);
56
+
57
+ // Wait for cooldown to expire
58
+ await new Promise((resolve) => setTimeout(resolve, shortCooldown + 10));
59
+
60
+ // Second request after cooldown should pass
61
+ middleware(req as Request, res as Response, next);
62
+ expect(next).toHaveBeenCalledTimes(2);
63
+ });
64
+
65
+ it('treats emails case-insensitively', () => {
66
+ const middleware = perEmailCooldown(60000);
67
+ const uniqueEmail = `case-test-${Date.now()}@example.com`;
68
+
69
+ // First request with mixed case
70
+ req.body = { email: uniqueEmail.toUpperCase() };
71
+ middleware(req as Request, res as Response, next);
72
+ expect(next).toHaveBeenCalledOnce();
73
+
74
+ // Second request with lowercase should be blocked
75
+ req.body = { email: uniqueEmail.toLowerCase() };
76
+ expect(() => {
77
+ middleware(req as Request, res as Response, next);
78
+ }).toThrow(AppError);
79
+ });
80
+
81
+ it('allows requests for different emails', () => {
82
+ const middleware = perEmailCooldown(60000);
83
+
84
+ // Request for first email
85
+ req.body = { email: 'user1@example.com' };
86
+ middleware(req as Request, res as Response, next);
87
+ expect(next).toHaveBeenCalledTimes(1);
88
+
89
+ // Request for second email should also pass
90
+ req.body = { email: 'user2@example.com' };
91
+ middleware(req as Request, res as Response, next);
92
+ expect(next).toHaveBeenCalledTimes(2);
93
+ });
94
+
95
+ it('passes through when no email in body', () => {
96
+ req.body = {}; // No email
97
+ const middleware = perEmailCooldown(60000);
98
+
99
+ middleware(req as Request, res as Response, next);
100
+
101
+ expect(next).toHaveBeenCalledOnce();
102
+ expect(next).toHaveBeenCalledWith();
103
+ });
104
+
105
+ it('calculates remaining cooldown time correctly', () => {
106
+ req.body = { email: 'timing@example.com' };
107
+ const cooldownMs = 60000;
108
+ const middleware = perEmailCooldown(cooldownMs);
109
+
110
+ // First request
111
+ middleware(req as Request, res as Response, next);
112
+
113
+ // Try second request immediately
114
+ try {
115
+ middleware(req as Request, res as Response, next);
116
+ } catch (error) {
117
+ if (error instanceof AppError) {
118
+ // Should show approximately 60 seconds remaining
119
+ expect(error.message).toMatch(/wait (\d+) seconds/);
120
+ const match = error.message.match(/wait (\d+) seconds/);
121
+ if (match) {
122
+ const seconds = parseInt(match[1]);
123
+ expect(seconds).toBeGreaterThanOrEqual(59);
124
+ expect(seconds).toBeLessThanOrEqual(60);
125
+ }
126
+ }
127
+ }
128
+ });
129
+
130
+ it('uses custom cooldown duration', () => {
131
+ req.body = { email: 'custom@example.com' };
132
+ const customCooldown = 30000; // 30 seconds
133
+ const middleware = perEmailCooldown(customCooldown);
134
+
135
+ // First request
136
+ middleware(req as Request, res as Response, next);
137
+
138
+ // Second request should show 30 second cooldown
139
+ try {
140
+ middleware(req as Request, res as Response, next);
141
+ } catch (error) {
142
+ if (error instanceof AppError) {
143
+ expect(error.message).toMatch(/wait \d+ seconds/);
144
+ const match = error.message.match(/wait (\d+) seconds/);
145
+ if (match) {
146
+ const seconds = parseInt(match[1]);
147
+ expect(seconds).toBeGreaterThanOrEqual(29);
148
+ expect(seconds).toBeLessThanOrEqual(30);
149
+ }
150
+ }
151
+ }
152
+ });
153
+ });
154
+ });