@etus/bhono-app 0.1.5 → 0.1.7

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 (300) hide show
  1. package/dist/index.js +0 -0
  2. package/package.json +7 -2
  3. package/templates/base/.claude/commands/check-skill-rules.md +112 -29
  4. package/templates/base/.claude/commands/linear/implement-issue.md +383 -55
  5. package/templates/base/.claude/commands/ship.md +77 -13
  6. package/templates/base/.claude/hooks/package-lock.json +0 -419
  7. package/templates/base/.claude/hooks/skill-activation-prompt.ts +185 -113
  8. package/templates/base/.claude/hooks/skill-tool-guard.sh +6 -0
  9. package/templates/base/.claude/hooks/skill-tool-guard.ts +198 -0
  10. package/templates/base/.claude/scripts/validate-skill-rules.sh +55 -32
  11. package/templates/base/.claude/settings.json +18 -11
  12. package/templates/base/.claude/skills/skill-rules.json +326 -173
  13. package/templates/base/.env.example +3 -0
  14. package/templates/base/CLAUDE.md +5 -5
  15. package/templates/base/README.md +40 -27
  16. package/templates/base/config/eslint.config.js +1 -0
  17. package/templates/base/config/wrangler.json +16 -17
  18. package/templates/base/docs/SETUP-GUIDE.md +566 -0
  19. package/templates/base/docs/app_spec.txt +13 -10
  20. package/templates/base/docs/architecture/README.md +162 -5
  21. package/templates/base/docs/architecture/api-catalog.md +575 -0
  22. package/templates/base/docs/architecture/c4-component.md +309 -0
  23. package/templates/base/docs/architecture/c4-container.md +183 -0
  24. package/templates/base/docs/architecture/c4-context.md +106 -0
  25. package/templates/base/docs/architecture/data-requirements.md +4 -3
  26. package/templates/base/docs/architecture/db-bootstrap.md +39 -0
  27. package/templates/base/docs/architecture/dependencies.md +327 -0
  28. package/templates/base/docs/architecture/drizzle-migration-plan.md +125 -0
  29. package/templates/base/docs/architecture/erd.md +1 -1
  30. package/templates/base/docs/architecture/sql-standards.md +100 -0
  31. package/templates/base/docs/architecture/tech-debt.md +184 -0
  32. package/templates/base/docs/testing.md +36 -29
  33. package/templates/base/package.json +26 -20
  34. package/templates/base/schema.sql +84 -0
  35. package/templates/base/scripts/capture-prod-session.ts +2 -2
  36. package/templates/base/scripts/init.sh +244 -59
  37. package/templates/base/scripts/sync-template.sh +104 -0
  38. package/templates/base/src/client/hooks/use-auth.ts +5 -0
  39. package/templates/base/src/client/routes/_authenticated/dashboard.tsx +1 -1
  40. package/templates/base/src/client/routes/index.tsx +1 -1
  41. package/templates/base/src/server/db/client.ts +3 -5
  42. package/templates/base/src/server/db/records.ts +81 -0
  43. package/templates/base/src/server/db/seed.ts +3 -2
  44. package/templates/base/src/server/db/sql.ts +116 -0
  45. package/templates/base/src/server/index.ts +17 -2
  46. package/templates/base/src/server/lib/audit.ts +74 -26
  47. package/templates/base/src/server/lib/audited-db.ts +219 -109
  48. package/templates/base/src/server/lib/transaction.ts +10 -16
  49. package/templates/base/src/server/middleware/account.ts +9 -16
  50. package/templates/base/src/server/middleware/auth.ts +102 -38
  51. package/templates/base/src/server/middleware/rate-limit.ts +8 -6
  52. package/templates/base/src/server/routes/accounts/handlers.ts +18 -6
  53. package/templates/base/src/server/routes/audits/handlers.ts +3 -1
  54. package/templates/base/src/server/routes/auth/handlers.ts +15 -10
  55. package/templates/base/src/server/routes/auth/test-login.ts +99 -45
  56. package/templates/base/src/server/routes/health/handlers.ts +4 -4
  57. package/templates/base/src/server/routes/index.ts +9 -0
  58. package/templates/base/src/server/routes/invitations/handlers.ts +9 -6
  59. package/templates/base/src/server/routes/openapi.ts +1 -1
  60. package/templates/base/src/server/routes/users/handlers.ts +21 -14
  61. package/templates/base/src/server/services/accounts.ts +242 -217
  62. package/templates/base/src/server/services/audits.ts +114 -61
  63. package/templates/base/src/server/services/auth.ts +310 -180
  64. package/templates/base/src/server/services/invitations.ts +282 -222
  65. package/templates/base/src/server/services/users.ts +383 -293
  66. package/templates/base/src/server/types/index.ts +1 -2
  67. package/templates/base/src/shared/types/api.ts +66 -198
  68. package/templates/base/tests/e2e/auth.setup.ts +1 -1
  69. package/templates/base/{src/server/__tests__/fixtures.ts → tests/fixtures/server.ts} +3 -3
  70. package/templates/base/{src/client/__tests__/setup-browser.ts → tests/helpers/client-setup-browser.ts} +2 -2
  71. package/templates/base/{src/client/__tests__/setup.ts → tests/helpers/client-setup.ts} +1 -1
  72. package/templates/base/{src/client/__tests__/test-utils.tsx → tests/helpers/client-test-utils.tsx} +2 -2
  73. package/templates/base/{src/server/__tests__/setup.ts → tests/helpers/server.ts} +9 -9
  74. package/templates/base/tests/integration/accounts/crud.test.ts +2 -11
  75. package/templates/base/tests/integration/audits/list.test.ts +2 -11
  76. package/templates/base/tests/integration/auth/auth-service.test.ts +1 -10
  77. package/templates/base/tests/integration/auth/invitation-token.test.ts +2 -11
  78. package/templates/base/tests/integration/auth/logout.test.ts +2 -11
  79. package/templates/base/tests/integration/auth/oauth.test.ts +23 -42
  80. package/templates/base/tests/integration/auth/refresh-token.test.ts +1 -9
  81. package/templates/base/tests/integration/auth/session-expiry.test.ts +1 -9
  82. package/templates/base/tests/integration/auth/session.test.ts +2 -11
  83. package/templates/base/tests/integration/auth/super-admin.test.ts +1 -9
  84. package/templates/base/tests/integration/authorization/analytics-role.test.ts +2 -11
  85. package/templates/base/tests/integration/authorization/billing-role.test.ts +2 -11
  86. package/templates/base/tests/integration/authorization/guards-roles.test.ts +1 -9
  87. package/templates/base/tests/integration/authorization/multi-tenancy.test.ts +2 -11
  88. package/templates/base/tests/integration/authorization/roles.test.ts +2 -11
  89. package/templates/base/tests/integration/config/production-behavior.test.ts +2 -11
  90. package/templates/base/tests/integration/health/health.test.ts +25 -44
  91. package/templates/base/tests/integration/invitations/crud.test.ts +2 -11
  92. package/templates/base/tests/integration/invitations/email.test.ts +1 -9
  93. package/templates/base/tests/integration/middleware/auth.test.ts +3 -12
  94. package/templates/base/tests/integration/middleware/request-logger.test.ts +1 -9
  95. package/templates/base/tests/integration/performance/response-times.test.ts +1 -9
  96. package/templates/base/tests/integration/security/cookie-security.test.ts +2 -11
  97. package/templates/base/tests/integration/security/csrf-protection.test.ts +2 -11
  98. package/templates/base/tests/integration/security/log-sanitization.test.ts +1 -9
  99. package/templates/base/tests/integration/security/rate-limiting.test.ts +1 -9
  100. package/templates/base/tests/integration/security/sql-injection.test.ts +7 -18
  101. package/templates/base/tests/integration/security/xss-prevention.test.ts +2 -11
  102. package/templates/base/tests/integration/setup.ts +13 -90
  103. package/templates/base/tests/integration/smoke.test.ts +3 -2
  104. package/templates/base/tests/integration/storage/upload.test.ts +2 -11
  105. package/templates/base/tests/integration/storage/validation.test.ts +2 -11
  106. package/templates/base/tests/integration/users/crud.test.ts +2 -11
  107. package/templates/base/tests/integration/users/list.test.ts +2 -11
  108. package/templates/base/tests/integration/vitest.config.ts +2 -9
  109. package/templates/base/{src/server/__tests__ → tests}/mocks/db.ts +1 -1
  110. package/templates/base/{src/server/__tests__ → tests}/mocks/index.ts +1 -1
  111. package/templates/base/{src/server/__tests__ → tests}/mocks/kv.ts +1 -1
  112. package/templates/base/{src/server/__tests__ → tests}/mocks/r2.ts +1 -1
  113. package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/sidebar.test.tsx +1 -1
  114. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/avatar.test.tsx +1 -1
  115. package/templates/base/{src/client/__tests__ → tests/unit/client/components/ui}/button.test.tsx +1 -1
  116. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/card.test.tsx +1 -1
  117. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/dialog.test.tsx +1 -1
  118. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/input.test.tsx +1 -1
  119. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/loading-skeleton.test.tsx +1 -1
  120. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/skeleton.test.tsx +1 -1
  121. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/sonner.test.tsx +1 -1
  122. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/tabs.test.tsx +1 -1
  123. package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/account.test.tsx +1 -1
  124. package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/integrations.test.tsx +1 -1
  125. package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/settings.test.tsx +1 -1
  126. package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/team.test.tsx +1 -1
  127. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/authenticated-layout.test.tsx +1 -1
  128. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/dashboard.test.tsx +1 -1
  129. package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/invite.test.tsx +1 -1
  130. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/login.test.tsx +1 -1
  131. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/navigation.test.tsx +1 -1
  132. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/root-layout.test.tsx +1 -1
  133. package/templates/base/{src/server/auth/__tests__ → tests/unit/server/auth}/guards.test.ts +2 -2
  134. package/templates/base/{src → tests/unit}/server/auth/permissions.test.ts +1 -1
  135. package/templates/base/{src → tests/unit}/server/auth/roles.test.ts +1 -1
  136. package/templates/base/tests/unit/server/db/sql.test.ts +68 -0
  137. package/templates/base/{src → tests/unit}/server/env.test.ts +1 -1
  138. package/templates/base/tests/unit/server/lib/audited-db.test.ts +78 -0
  139. package/templates/base/{src → tests/unit}/server/lib/email.test.ts +1 -1
  140. package/templates/base/{src → tests/unit}/server/lib/errors.test.ts +1 -1
  141. package/templates/base/{src → tests/unit}/server/lib/oauth.test.ts +1 -1
  142. package/templates/base/{src → tests/unit}/server/lib/pagination.test.ts +1 -1
  143. package/templates/base/{src → tests/unit}/server/lib/password.test.ts +1 -1
  144. package/templates/base/{src → tests/unit}/server/lib/providers.test.ts +1 -1
  145. package/templates/base/{src → tests/unit}/server/lib/r2-storage.test.ts +2 -2
  146. package/templates/base/{src → tests/unit}/server/lib/session.test.ts +2 -2
  147. package/templates/base/{src → tests/unit}/server/lib/tokens.test.ts +1 -1
  148. package/templates/base/{src → tests/unit}/server/lib/transaction.test.ts +5 -14
  149. package/templates/base/{src → tests/unit}/server/middleware/account.test.ts +16 -24
  150. package/templates/base/tests/unit/server/middleware/auth.test.ts +647 -0
  151. package/templates/base/{src → tests/unit}/server/middleware/cors.test.ts +1 -1
  152. package/templates/base/{src → tests/unit}/server/middleware/error-handler.test.ts +2 -2
  153. package/templates/base/{src → tests/unit}/server/middleware/rate-limit.test.ts +3 -2
  154. package/templates/base/{src → tests/unit}/server/middleware/request-context.test.ts +1 -1
  155. package/templates/base/{src → tests/unit}/server/middleware/request-logger.test.ts +1 -1
  156. package/templates/base/{src/server/__tests__/mocks/__tests__ → tests/unit/server/mocks}/db.test.ts +1 -1
  157. package/templates/base/{src/server/__tests__/mocks/__tests__ → tests/unit/server/mocks}/kv.test.ts +1 -1
  158. package/templates/base/{src/server/__tests__/mocks/__tests__ → tests/unit/server/mocks}/r2.test.ts +1 -1
  159. package/templates/base/{src/server/routes/accounts/__tests__ → tests/unit/server/routes/accounts}/handlers.test.ts +12 -12
  160. package/templates/base/{src/server/routes/audits/__tests__ → tests/unit/server/routes/audits}/handlers.test.ts +11 -11
  161. package/templates/base/{src/server/routes/auth/__tests__ → tests/unit/server/routes/auth}/handlers.test.ts +124 -13
  162. package/templates/base/{src/server/routes/health/__tests__ → tests/unit/server/routes/health}/handlers.test.ts +27 -23
  163. package/templates/base/{src/server/routes/invitations/__tests__ → tests/unit/server/routes/invitations}/handlers.test.ts +14 -17
  164. package/templates/base/{src/server/routes/storage/__tests__ → tests/unit/server/routes/storage}/handlers.test.ts +6 -6
  165. package/templates/base/{src/server/routes/users/__tests__ → tests/unit/server/routes/users}/handlers.test.ts +81 -17
  166. package/templates/base/tests/unit/server/services/accounts.test.ts +406 -0
  167. package/templates/base/tests/unit/server/services/audits.test.ts +360 -0
  168. package/templates/base/tests/unit/server/services/auth.test.ts +656 -0
  169. package/templates/base/tests/unit/server/services/invitations.test.ts +343 -0
  170. package/templates/base/tests/unit/server/services/users.test.ts +706 -0
  171. package/templates/base/{src/shared/schemas/__tests__ → tests/unit/shared}/schemas.test.ts +1 -1
  172. package/templates/base/tsconfig.json +2 -1
  173. package/templates/base/vite.config.ts +3 -1
  174. package/templates/base/vitest.config.browser.ts +3 -2
  175. package/templates/base/vitest.config.frontend.ts +3 -2
  176. package/templates/base/vitest.config.ts +7 -14
  177. package/templates/base/.claude/settings.local.json +0 -11
  178. package/templates/base/.github/workflows/test.yml +0 -127
  179. package/templates/base/auth-setup-error.png +0 -0
  180. package/templates/base/config/drizzle.config.ts +0 -10
  181. package/templates/base/pnpm-lock.yaml +0 -8175
  182. package/templates/base/src/server/db/schema/accounts.ts +0 -20
  183. package/templates/base/src/server/db/schema/audit-logs.ts +0 -26
  184. package/templates/base/src/server/db/schema/index.ts +0 -7
  185. package/templates/base/src/server/db/schema/invitations.ts +0 -30
  186. package/templates/base/src/server/db/schema/refresh-tokens.ts +0 -22
  187. package/templates/base/src/server/db/schema/user-accounts.ts +0 -25
  188. package/templates/base/src/server/db/schema/users.ts +0 -33
  189. package/templates/base/src/server/lib/audited-db.test.ts +0 -107
  190. package/templates/base/src/server/lib/schema-helpers.ts +0 -16
  191. package/templates/base/src/server/middleware/auth.test.ts +0 -345
  192. package/templates/base/src/server/services/__tests__/accounts.test.ts +0 -764
  193. package/templates/base/src/server/services/__tests__/audits.test.ts +0 -235
  194. package/templates/base/src/server/services/__tests__/auth.test.ts +0 -765
  195. package/templates/base/src/server/services/__tests__/invitations.test.ts +0 -704
  196. package/templates/base/src/server/services/__tests__/users.test.ts +0 -755
  197. package/templates/base/tests/e2e/_auth/.gitkeep +0 -0
  198. package/templates/base/tests/integration/lib/schema-helpers.test.ts +0 -129
  199. package/templates/base/tsconfig.tsbuildinfo +0 -1
  200. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-can-be-collapsed-by-default-1.png +0 -0
  201. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-expands-when-collapsed-and-expand-button-is-clicked-1.png +0 -0
  202. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-handles-logout-button-click-1.png +0 -0
  203. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-handles-navigation-clicks-1.png +0 -0
  204. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-hides-navigation-labels-when-collapsed-1.png +0 -0
  205. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-highlights-active-route-1.png +0 -0
  206. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-renders-sidebar-navigation-items-1.png +0 -0
  207. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-shows-keyboard-shortcut-hint-when-expanded-1.png +0 -0
  208. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-info-when-authenticated-1.png +0 -0
  209. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-initials-in-avatar-fallback-1.png +0 -0
  210. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/error-boundary.test.tsx +0 -0
  211. /package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/error-fallback.test.tsx +0 -0
  212. /package/templates/base/{src/client/hooks/__tests__ → tests/unit/client/hooks}/use-auth.test.tsx +0 -0
  213. /package/templates/base/{src/client/hooks/__tests__ → tests/unit/client/hooks}/use-theme.test.tsx +0 -0
  214. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-dashboard-stats-cards-1.png +0 -0
  215. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-quick-action-cards-1.png +0 -0
  216. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-recent-activity-section-1.png +0 -0
  217. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-first-name-in-welcome-message-1.png +0 -0
  218. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-information-in-sidebar-1.png +0 -0
  219. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-render-dashboard-when-authenticated-1.png +0 -0
  220. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-show-navigation-sidebar-1.png +0 -0
  221. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-unauthenticated-should-redirect-to-login-when-not-authenticated-1.png +0 -0
  222. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-accept-invitation-button-1.png +0 -0
  223. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-decline-button-linking-to-homepage-1.png +0 -0
  224. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-invitation-details--email--workspace--role--1.png +0 -0
  225. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-have-a-logo-link-to-homepage-1.png +0 -0
  226. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-render-invitation-page-with-inviter-name-and-workspace-1.png +0 -0
  227. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-show-terms-of-service-and-privacy-policy-links-1.png +0 -0
  228. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-display-Google-OAuth-login-button-1.png +0 -0
  229. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-have-a-link-back-to-home-page-1.png +0 -0
  230. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-content-without-waiting-for-authentication-1.png +0 -0
  231. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-page-at--login-route-1.png +0 -0
  232. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-show-Terms-of-Service-and-Privacy-Policy-links-1.png +0 -0
  233. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-trigger-OAuth-flow-when-clicking-login-button-1.png +0 -0
  234. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-display-404-text-on-not-found-page-1.png +0 -0
  235. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-have-navigation-options-on-404-page-1.png +0 -0
  236. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-render-404-page-for-unknown-routes-1.png +0 -0
  237. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-account-page-1.png +0 -0
  238. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-integrations-page-1.png +0 -0
  239. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-settings-page-1.png +0 -0
  240. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-team-page-1.png +0 -0
  241. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-display-navigation-links-on-home-page-1.png +0 -0
  242. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-have-correct-link-destinations-on-home-page-1.png +0 -0
  243. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-home-page-without-authentication-1.png +0 -0
  244. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-login-page-without-authentication-1.png +0 -0
  245. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-dashboard-to-login-1.png +0 -0
  246. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-settings-to-login-1.png +0 -0
  247. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-team-to-login-1.png +0 -0
  248. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-render-Active-Sessions-section-with-session-cards-1.png +0 -0
  249. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-render-page-with-correct-title--Account--1.png +0 -0
  250. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-show-API-Access-section-1.png +0 -0
  251. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-show-Connected-Accounts-section-with-Google-connected-1.png +0 -0
  252. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-show-Danger-Zone-section-with-delete-button-1.png +0 -0
  253. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-show-Security-section-with-Two-Factor-Authentication-1.png +0 -0
  254. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-API-documentation-section-should-display-API-documentation-link-section-1.png +0 -0
  255. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-Create-Webhook-Dialog-should-have-Add-Webhook-trigger-button-1.png +0 -0
  256. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-category-filters-should-display-all-category-buttons-1.png +0 -0
  257. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-all-integration-cards-with-names-1.png +0 -0
  258. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-category-badges-on-cards-1.png +0 -0
  259. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Configure-button-for-connected-integrations-1.png +0 -0
  260. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connect-button-for-not-connected-integrations-1.png +0 -0
  261. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connected-badge-for-connected-integrations-1.png +0 -0
  262. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Available-Integrations-section-1.png +0 -0
  263. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Webhooks-section-1.png +0 -0
  264. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-connected-count-1.png +0 -0
  265. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-page-description-1.png +0 -0
  266. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-search-input-1.png +0 -0
  267. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-render-with-correct-title--Integrations--1.png +0 -0
  268. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-filter-integrations-based-on-search-query-1.png +0 -0
  269. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-search-by-description-1.png +0 -0
  270. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-show-no-results-message-when-search-has-no-matches-1.png +0 -0
  271. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-existing-webhook-1.png +0 -0
  272. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-events-1.png +0 -0
  273. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-last-delivery-info-1.png +0 -0
  274. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-success-status-1.png +0 -0
  275. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-have-Add-Webhook-button-1.png +0 -0
  276. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-connected-accounts-section-with-Google-provider-1.png +0 -0
  277. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-sessions-and-danger-zone-sections-1.png +0 -0
  278. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-all-notification-toggle-options-1.png +0 -0
  279. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-email-notifications-section-1.png +0 -0
  280. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-toggle-switches-for-notification-options-1.png +0 -0
  281. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-have-toggles-checked-by-default-1.png +0 -0
  282. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display--Save-Changes--button-1.png +0 -0
  283. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-personal-information-form-1.png +0 -0
  284. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-profile-picture-section-1.png +0 -0
  285. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-email-in-disabled-email-input-1.png +0 -0
  286. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-initials-in-avatar-1.png +0 -0
  287. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-name-in-the-name-input-1.png +0 -0
  288. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-all-three-tabs-1.png +0 -0
  289. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-page-description-1.png +0 -0
  290. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-render-with-correct-title--Settings--1.png +0 -0
  291. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-show-Profile-tab-as-default-active-tab-1.png +0 -0
  292. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Account-tab-when-clicked-1.png +0 -0
  293. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Notifications-tab-when-clicked-1.png +0 -0
  294. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-display-Active-Members-section-with-member-count-1.png +0 -0
  295. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-display-Pending-Invitations-section-with-invitation-details-1.png +0 -0
  296. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-have-invite-member-button-that-can-be-clicked-1.png +0 -0
  297. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-render-page-with-correct-title-and-description-1.png +0 -0
  298. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-render-search-input-that-filters-team-members-1.png +0 -0
  299. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-show-current-user-with---you---indicator-and-role-badge-1.png +0 -0
  300. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/error-components.test.tsx +0 -0
@@ -1,10 +1,9 @@
1
1
  // src/middleware/account.ts
2
2
  import { createMiddleware } from 'hono/factory'
3
3
  import { HTTPException } from 'hono/http-exception'
4
- import { userAccounts } from '../db/schema'
5
- import { eq, and } from 'drizzle-orm'
6
4
  import type { HonoEnv } from '../types'
7
5
  import type { Role } from '../auth/roles'
6
+ import { queryOne } from '../db/sql'
8
7
 
9
8
  export const accountMiddleware = createMiddleware<HonoEnv>(async (c, next) => {
10
9
  // Check account-id header (required)
@@ -36,21 +35,15 @@ export const accountMiddleware = createMiddleware<HonoEnv>(async (c, next) => {
36
35
 
37
36
  // Check user-account membership in database
38
37
  const db = c.get('db')
39
- if (!db) {
38
+ const accountDb = c.env.DB ?? db
39
+ if (!accountDb) {
40
40
  throw new HTTPException(500, { message: 'Database not initialized' })
41
41
  }
42
- const membershipResults = await db
43
- .select()
44
- .from(userAccounts)
45
- .where(
46
- and(
47
- eq(userAccounts.userId, user.id),
48
- eq(userAccounts.accountId, accountId)
49
- )
50
- )
51
- .limit(1)
52
-
53
- const membership = membershipResults.at(0)
42
+ const membership = await queryOne<{ role: Role }>(
43
+ accountDb,
44
+ `SELECT role FROM user_accounts WHERE user_id = ? AND account_id = ? LIMIT 1`,
45
+ [user.id, accountId]
46
+ )
54
47
  if (!membership) {
55
48
  throw new HTTPException(403, {
56
49
  message: 'Forbidden: User does not have access to this account',
@@ -59,7 +52,7 @@ export const accountMiddleware = createMiddleware<HonoEnv>(async (c, next) => {
59
52
 
60
53
  // Set accountId and userRole in context
61
54
  c.set('accountId', accountId)
62
- c.set('userRole', membership.role as Role)
55
+ c.set('userRole', membership.role)
63
56
  c.set('isSystemAdminAccess', false)
64
57
 
65
58
  await next()
@@ -2,10 +2,9 @@
2
2
  import { createMiddleware } from 'hono/factory'
3
3
  import { verify } from 'hono/jwt'
4
4
  import { HTTPException } from 'hono/http-exception'
5
- import { users } from '../db/schema'
6
- import { eq, and, isNull } from 'drizzle-orm'
7
5
  import type { HonoEnv } from '../types'
8
6
  import { getSession } from '../lib/session'
7
+ import { queryOne, toStringValue, toNullableString, type SqlRow } from '../db/sql'
9
8
 
10
9
  interface JWTPayload {
11
10
  sub: string
@@ -14,6 +13,63 @@ interface JWTPayload {
14
13
  exp: number
15
14
  }
16
15
 
16
+
17
+ const USER_SELECT_COLUMNS = `
18
+ id,
19
+ email,
20
+ name,
21
+ status,
22
+ provider_ids as providerIds,
23
+ is_super_admin as isSuperAdmin,
24
+ created_at as createdAt,
25
+ updated_at as updatedAt,
26
+ deleted_at as deletedAt
27
+ `
28
+
29
+
30
+ function parseProviderIds(value: unknown): string[] {
31
+ if (!value) return []
32
+ if (Array.isArray(value)) return value.map((item) => String(item))
33
+ if (typeof value === 'string') {
34
+ try {
35
+ const parsed = JSON.parse(value) as unknown
36
+ if (Array.isArray(parsed)) {
37
+ return parsed.map((item) => String(item))
38
+ }
39
+ } catch {
40
+ return []
41
+ }
42
+ }
43
+ return []
44
+ }
45
+
46
+ function toBoolean(value: unknown): boolean {
47
+ if (typeof value === 'boolean') return value
48
+ if (typeof value === 'number') return value !== 0
49
+ if (typeof value === 'string') return value === '1' || value.toLowerCase() === 'true'
50
+ return false
51
+ }
52
+
53
+ function mapUserRow(row: SqlRow) {
54
+ const providerIds = row.providerIds ?? row.provider_ids
55
+ const isSuperAdmin = row.isSuperAdmin ?? row.is_super_admin
56
+ const createdAt = row.createdAt ?? row.created_at
57
+ const updatedAt = row.updatedAt ?? row.updated_at
58
+ const deletedAt = row.deletedAt ?? row.deleted_at
59
+
60
+ return {
61
+ id: toStringValue(row.id),
62
+ email: toStringValue(row.email),
63
+ name: toStringValue(row.name),
64
+ status: row.status === 'inactive' ? 'inactive' : 'active',
65
+ providerIds: parseProviderIds(providerIds),
66
+ isSuperAdmin: toBoolean(isSuperAdmin),
67
+ createdAt: toStringValue(createdAt),
68
+ updatedAt: toStringValue(updatedAt),
69
+ deletedAt: toNullableString(deletedAt),
70
+ }
71
+ }
72
+
17
73
  /**
18
74
  * Session-based authentication middleware
19
75
  * Validates session from cookie and loads user from database
@@ -29,23 +85,27 @@ export const sessionAuth = createMiddleware<HonoEnv>(async (c, next) => {
29
85
 
30
86
  // Look up user in database to ensure they still exist and are active
31
87
  const db = c.get('db')
32
- if (!db) {
88
+ const authDb = c.env.DB ?? db
89
+ if (!authDb) {
33
90
  throw new HTTPException(500, { message: 'Database not initialized' })
34
91
  }
35
- const userResults = await db
36
- .select()
37
- .from(users)
38
- .where(and(eq(users.id, session.userId), isNull(users.deletedAt)))
39
- .limit(1)
40
-
41
- const user = userResults.at(0)
92
+ const user = await queryOne(
93
+ authDb,
94
+ `SELECT ${USER_SELECT_COLUMNS}
95
+ FROM users
96
+ WHERE id = ? AND deleted_at IS NULL
97
+ LIMIT 1`,
98
+ [session.userId]
99
+ )
42
100
  if (!user) {
43
101
  throw new HTTPException(401, {
44
102
  message: 'User not found',
45
103
  })
46
104
  }
47
105
 
48
- if (user.status !== 'active') {
106
+ const mappedUser = mapUserRow(user)
107
+
108
+ if (mappedUser.status !== 'active') {
49
109
  throw new HTTPException(401, {
50
110
  message: 'User account is not active',
51
111
  })
@@ -53,15 +113,15 @@ export const sessionAuth = createMiddleware<HonoEnv>(async (c, next) => {
53
113
 
54
114
  // Set user in context
55
115
  c.set('user', {
56
- id: user.id,
57
- email: user.email,
58
- name: user.name,
59
- status: user.status,
60
- providerIds: user.providerIds ?? [],
61
- isSuperAdmin: user.isSuperAdmin,
62
- createdAt: user.createdAt,
63
- updatedAt: user.updatedAt,
64
- deletedAt: user.deletedAt,
116
+ id: mappedUser.id,
117
+ email: mappedUser.email,
118
+ name: mappedUser.name,
119
+ status: mappedUser.status,
120
+ providerIds: mappedUser.providerIds,
121
+ isSuperAdmin: mappedUser.isSuperAdmin,
122
+ createdAt: mappedUser.createdAt,
123
+ updatedAt: mappedUser.updatedAt,
124
+ deletedAt: mappedUser.deletedAt,
65
125
  })
66
126
 
67
127
  await next()
@@ -107,23 +167,27 @@ export const jwtAuth = createMiddleware<HonoEnv>(async (c, next) => {
107
167
 
108
168
  // Look up user in database by ID (from sub claim)
109
169
  const db = c.get('db')
110
- if (!db) {
170
+ const authDb = c.env.DB ?? db
171
+ if (!authDb) {
111
172
  throw new HTTPException(500, { message: 'Database not initialized' })
112
173
  }
113
- const userResults = await db
114
- .select()
115
- .from(users)
116
- .where(and(eq(users.id, payload.sub), isNull(users.deletedAt)))
117
- .limit(1)
118
-
119
- const user = userResults.at(0)
174
+ const user = await queryOne(
175
+ authDb,
176
+ `SELECT ${USER_SELECT_COLUMNS}
177
+ FROM users
178
+ WHERE id = ? AND deleted_at IS NULL
179
+ LIMIT 1`,
180
+ [payload.sub]
181
+ )
120
182
  if (!user) {
121
183
  throw new HTTPException(401, {
122
184
  message: 'User not found',
123
185
  })
124
186
  }
125
187
 
126
- if (user.status !== 'active') {
188
+ const mappedUser = mapUserRow(user)
189
+
190
+ if (mappedUser.status !== 'active') {
127
191
  throw new HTTPException(401, {
128
192
  message: 'User account is not active',
129
193
  })
@@ -131,15 +195,15 @@ export const jwtAuth = createMiddleware<HonoEnv>(async (c, next) => {
131
195
 
132
196
  // Set user in context
133
197
  c.set('user', {
134
- id: user.id,
135
- email: user.email,
136
- name: user.name,
137
- status: user.status,
138
- providerIds: user.providerIds ?? [],
139
- isSuperAdmin: user.isSuperAdmin,
140
- createdAt: user.createdAt,
141
- updatedAt: user.updatedAt,
142
- deletedAt: user.deletedAt,
198
+ id: mappedUser.id,
199
+ email: mappedUser.email,
200
+ name: mappedUser.name,
201
+ status: mappedUser.status,
202
+ providerIds: mappedUser.providerIds,
203
+ isSuperAdmin: mappedUser.isSuperAdmin,
204
+ createdAt: mappedUser.createdAt,
205
+ updatedAt: mappedUser.updatedAt,
206
+ deletedAt: mappedUser.deletedAt,
143
207
  })
144
208
 
145
209
  await next()
@@ -49,11 +49,8 @@ class RateLimitStore {
49
49
  private cleanupInterval: ReturnType<typeof setInterval> | null = null
50
50
  private lastCleanup = 0
51
51
 
52
- constructor() {
53
- // Don't use setInterval at construction time - Cloudflare Workers
54
- // don't allow async I/O at global scope. Instead, we'll cleanup
55
- // lazily during request processing.
56
- }
52
+ // Note: Don't use setInterval at construction time - Cloudflare Workers
53
+ // don't allow async I/O at global scope. Instead, we cleanup lazily.
57
54
 
58
55
  /**
59
56
  * Start periodic cleanup (call from within a handler, not at global scope)
@@ -240,8 +237,13 @@ export function rateLimit(options: RateLimitOptions = {}) {
240
237
  export function authRateLimit() {
241
238
  return rateLimit({
242
239
  windowMs: 60000, // 1 minute
243
- max: 10, // 10 requests per minute for auth endpoints
240
+ max: 10, // 10 requests per minute for login endpoints (brute force protection)
244
241
  message: 'Too many authentication attempts, please try again later',
242
+ // Use separate key prefix to not conflict with global rate limit
243
+ keyGenerator: (c) => {
244
+ const ip = c.get('ip') ?? c.req.header('x-forwarded-for')?.split(',')[0].trim() ?? 'unknown'
245
+ return `auth:${ip}` // Different prefix than global rate limit
246
+ },
245
247
  })
246
248
  }
247
249
 
@@ -13,6 +13,7 @@ import type {
13
13
  export const listAccountsHandler: RouteHandler<typeof listAccountsRoute, HonoEnv> = async (c) => {
14
14
  const query = c.req.valid('query')
15
15
  const db = c.get('db')
16
+ const envDb = c.env.DB
16
17
  const accountId = c.get('accountId')
17
18
  const user = c.get('user')
18
19
  const transactionId = c.get('transactionId')
@@ -31,7 +32,8 @@ export const listAccountsHandler: RouteHandler<typeof listAccountsRoute, HonoEnv
31
32
  userAgent,
32
33
  }
33
34
 
34
- const result = await accountsService.findAll(db, ctx, {
35
+ const accountsDb = envDb ?? db
36
+ const result = await accountsService.findAll(accountsDb, ctx, {
35
37
  page: query.page,
36
38
  limit: query.limit,
37
39
  sortBy: query.sortBy,
@@ -45,6 +47,7 @@ export const listAccountsHandler: RouteHandler<typeof listAccountsRoute, HonoEnv
45
47
  export const getAccountHandler: RouteHandler<typeof getAccountRoute, HonoEnv> = async (c) => {
46
48
  const { id } = c.req.valid('param')
47
49
  const db = c.get('db')
50
+ const envDb = c.env.DB
48
51
  const accountId = c.get('accountId')
49
52
  const user = c.get('user')
50
53
  const transactionId = c.get('transactionId')
@@ -63,13 +66,15 @@ export const getAccountHandler: RouteHandler<typeof getAccountRoute, HonoEnv> =
63
66
  userAgent,
64
67
  }
65
68
 
66
- const account = await accountsService.findById(db, ctx, id)
69
+ const accountsDb = envDb ?? db
70
+ const account = await accountsService.findById(accountsDb, ctx, id)
67
71
  return c.json({ data: account }, 200)
68
72
  }
69
73
 
70
74
  export const createAccountHandler: RouteHandler<typeof createAccountRoute, HonoEnv> = async (c) => {
71
75
  const data = c.req.valid('json')
72
76
  const db = c.get('db')
77
+ const envDb = c.env.DB
73
78
  const accountId = c.get('accountId')
74
79
  const user = c.get('user')
75
80
  const transactionId = c.get('transactionId')
@@ -88,7 +93,8 @@ export const createAccountHandler: RouteHandler<typeof createAccountRoute, HonoE
88
93
  userAgent,
89
94
  }
90
95
 
91
- const newAccount = await accountsService.create(db, ctx, {
96
+ const accountsDb = envDb ?? db
97
+ const newAccount = await accountsService.create(accountsDb, ctx, {
92
98
  name: data.name,
93
99
  description: data.description,
94
100
  domain: data.domain,
@@ -101,6 +107,7 @@ export const updateAccountHandler: RouteHandler<typeof updateAccountRoute, HonoE
101
107
  const { id } = c.req.valid('param')
102
108
  const data = c.req.valid('json')
103
109
  const db = c.get('db')
110
+ const envDb = c.env.DB
104
111
  const accountId = c.get('accountId')
105
112
  const user = c.get('user')
106
113
  const transactionId = c.get('transactionId')
@@ -119,7 +126,8 @@ export const updateAccountHandler: RouteHandler<typeof updateAccountRoute, HonoE
119
126
  userAgent,
120
127
  }
121
128
 
122
- const updatedAccount = await accountsService.update(db, ctx, id, {
129
+ const accountsDb = envDb ?? db
130
+ const updatedAccount = await accountsService.update(accountsDb, ctx, id, {
123
131
  name: data.name,
124
132
  description: data.description,
125
133
  domain: data.domain,
@@ -131,6 +139,7 @@ export const updateAccountHandler: RouteHandler<typeof updateAccountRoute, HonoE
131
139
  export const deleteAccountHandler: RouteHandler<typeof deleteAccountRoute, HonoEnv> = async (c) => {
132
140
  const { id } = c.req.valid('param')
133
141
  const db = c.get('db')
142
+ const envDb = c.env.DB
134
143
  const accountId = c.get('accountId')
135
144
  const user = c.get('user')
136
145
  const transactionId = c.get('transactionId')
@@ -149,13 +158,15 @@ export const deleteAccountHandler: RouteHandler<typeof deleteAccountRoute, HonoE
149
158
  userAgent,
150
159
  }
151
160
 
152
- await accountsService.delete(db, ctx, id)
161
+ const accountsDb = envDb ?? db
162
+ await accountsService.delete(accountsDb, ctx, id)
153
163
  return c.body(null, 204)
154
164
  }
155
165
 
156
166
  export const restoreAccountHandler: RouteHandler<typeof restoreAccountRoute, HonoEnv> = async (c) => {
157
167
  const { id } = c.req.valid('param')
158
168
  const db = c.get('db')
169
+ const envDb = c.env.DB
159
170
  const accountId = c.get('accountId')
160
171
  const user = c.get('user')
161
172
  const transactionId = c.get('transactionId')
@@ -174,6 +185,7 @@ export const restoreAccountHandler: RouteHandler<typeof restoreAccountRoute, Hon
174
185
  userAgent,
175
186
  }
176
187
 
177
- const result = await accountsService.restore(db, ctx, id)
188
+ const accountsDb = envDb ?? db
189
+ const result = await accountsService.restore(accountsDb, ctx, id)
178
190
  return c.json({ data: result }, 200)
179
191
  }
@@ -7,6 +7,7 @@ import type { listAuditLogsRoute } from './routes'
7
7
  export const listAuditLogsHandler: RouteHandler<typeof listAuditLogsRoute, HonoEnv> = async (c) => {
8
8
  const query = c.req.valid('query')
9
9
  const db = c.get('db')
10
+ const envDb = c.env.DB
10
11
  const accountId = c.get('accountId')
11
12
  const user = c.get('user')
12
13
  const transactionId = c.get('transactionId')
@@ -25,7 +26,8 @@ export const listAuditLogsHandler: RouteHandler<typeof listAuditLogsRoute, HonoE
25
26
  userAgent,
26
27
  }
27
28
 
28
- const result = await auditsService.findAll(db, ctx, {
29
+ const auditsDb = envDb ?? db
30
+ const result = await auditsService.findAll(auditsDb, ctx, {
29
31
  page: query.page,
30
32
  limit: query.limit,
31
33
  entity: query.entity,
@@ -64,9 +64,11 @@ export const callbackHandler: RouteHandler<typeof callbackRoute, HonoEnv> = asyn
64
64
  const { code, state } = c.req.valid('query')
65
65
  const ctx = getAuthContext(c)
66
66
 
67
- if (!db) {
67
+ const authDb = env.DB ?? db
68
+ if (!authDb) {
68
69
  throw new HTTPException(500, { message: 'Database not initialized' })
69
70
  }
71
+ const inviteDb = authDb
70
72
 
71
73
  // Get stored OAuth state
72
74
  const oauthCookie = getCookie(c, 'oauth_state')
@@ -99,16 +101,16 @@ export const callbackHandler: RouteHandler<typeof callbackRoute, HonoEnv> = asyn
99
101
  const googleUser = decodeIdToken(tokens.id_token)
100
102
 
101
103
  // Find or create user
102
- const result = await authService.findOrCreateUser(db, env, googleUser, ctx)
104
+ const result = await authService.findOrCreateUser(authDb, env, googleUser, ctx)
103
105
 
104
106
  // Check for pending invitation
105
107
  const pendingInvitation = getCookie(c, 'pending_invitation')
106
108
  if (pendingInvitation) {
107
109
  deleteCookie(c, 'pending_invitation')
108
110
 
109
- const invitation = await invitationsService.getByToken(db, pendingInvitation)
111
+ const invitation = await invitationsService.getByToken(inviteDb, pendingInvitation)
110
112
  if (invitation) {
111
- await invitationsService.accept(db, invitation.id, result.user.id, ctx)
113
+ await invitationsService.accept(inviteDb, invitation.id, result.user.id, ctx)
112
114
  }
113
115
  }
114
116
 
@@ -131,9 +133,10 @@ export const callbackHandler: RouteHandler<typeof callbackRoute, HonoEnv> = asyn
131
133
  export const refreshHandler: RouteHandler<typeof refreshRoute, HonoEnv> = async (c) => {
132
134
  const db = c.get('db')
133
135
  const env = c.env
136
+ const authDb = env.DB ?? db
134
137
  const refreshToken = getCookie(c, 'refresh_token')
135
138
 
136
- if (!db) {
139
+ if (!db || !authDb) {
137
140
  throw new HTTPException(500, { message: 'Database not initialized' })
138
141
  }
139
142
 
@@ -142,24 +145,25 @@ export const refreshHandler: RouteHandler<typeof refreshRoute, HonoEnv> = async
142
145
  }
143
146
 
144
147
  const ctx = getAuthContext(c)
145
- const tokens = await authService.refreshAccessToken(db, env, refreshToken, ctx)
148
+ const tokens = await authService.refreshAccessToken(authDb, env, refreshToken, ctx)
146
149
 
147
150
  return c.json({ tokens }, 200)
148
151
  }
149
152
 
150
153
  export const logoutHandler: RouteHandler<typeof logoutRoute, HonoEnv> = async (c) => {
151
154
  const db = c.get('db')
155
+ const authDb = c.env.DB ?? db
152
156
  const ctx = getAuthContext(c)
153
157
  const session = getSession(c)
154
158
 
155
- if (!db) {
159
+ if (!db || !authDb) {
156
160
  throw new HTTPException(500, { message: 'Database not initialized' })
157
161
  }
158
162
 
159
163
  // Log logout event if we have a session
160
164
  if (session) {
161
165
  const { logAuthEvent } = await import('../../lib/audit')
162
- await logAuthEvent(db, ctx, 'LOGOUT', session.userId, {})
166
+ await logAuthEvent(authDb, ctx, 'LOGOUT', session.userId, {})
163
167
  }
164
168
 
165
169
  // Destroy session (removes from KV and clears cookie)
@@ -195,14 +199,15 @@ export const inviteHandler: RouteHandler<typeof inviteRoute, HonoEnv> = async (c
195
199
  const env = c.env
196
200
  const { token } = c.req.valid('param')
197
201
 
198
- if (!db) {
202
+ const inviteDb = env.DB ?? db
203
+ if (!inviteDb) {
199
204
  throw new HTTPException(500, { message: 'Database not initialized' })
200
205
  }
201
206
 
202
207
  const isProduction = env.ENVIRONMENT === 'production'
203
208
 
204
209
  // Validate invitation
205
- const invitation = await invitationsService.getByToken(db, token)
210
+ const invitation = await invitationsService.getByToken(inviteDb, token)
206
211
 
207
212
  if (!invitation) {
208
213
  throw new HTTPException(400, { message: 'Invalid or expired invitation' })
@@ -1,9 +1,9 @@
1
1
  // src/server/routes/auth/test-login.ts
2
2
  import type { RouteHandler } from '@hono/zod-openapi'
3
3
  import { createRoute, z } from '@hono/zod-openapi'
4
- import { eq } from 'drizzle-orm'
5
4
  import { HTTPException } from 'hono/http-exception'
6
- import { users, accounts, userAccounts, type UserRecord } from '../../db/schema'
5
+ import { execute, queryOne, toStringValue, toNullableString, type SqlRow } from '../../db/sql'
6
+ import type { UserRecord } from '../../db/records'
7
7
  import { createSession } from '../../lib/session'
8
8
  import type { HonoEnv } from '../../types'
9
9
 
@@ -12,6 +12,42 @@ const TestLoginSchema = z.object({
12
12
  name: z.string().optional(),
13
13
  })
14
14
 
15
+ const USER_SELECT_COLUMNS = `
16
+ id,
17
+ google_id as googleId,
18
+ email,
19
+ name,
20
+ avatar_url as avatarUrl,
21
+ status,
22
+ is_super_admin as isSuperAdmin,
23
+ created_at as createdAt,
24
+ updated_at as updatedAt,
25
+ deleted_at as deletedAt
26
+ `
27
+
28
+ function toBoolean(value: unknown): boolean {
29
+ if (typeof value === 'boolean') return value
30
+ if (typeof value === 'number') return value !== 0
31
+ if (typeof value === 'string') return value === '1' || value.toLowerCase() === 'true'
32
+ return false
33
+ }
34
+
35
+ function mapUserRow(row: SqlRow): UserRecord {
36
+ return {
37
+ id: toStringValue(row.id),
38
+ googleId: toStringValue(row.googleId ?? row.google_id),
39
+ email: toStringValue(row.email),
40
+ name: toStringValue(row.name),
41
+ avatarUrl: toNullableString(row.avatarUrl),
42
+ status: row.status === 'inactive' ? 'inactive' : 'active',
43
+ providerIds: [],
44
+ isSuperAdmin: toBoolean(row.isSuperAdmin ?? row.is_super_admin),
45
+ createdAt: toStringValue(row.createdAt ?? row.created_at),
46
+ updatedAt: toStringValue(row.updatedAt ?? row.updated_at),
47
+ deletedAt: toNullableString(row.deletedAt),
48
+ }
49
+ }
50
+
15
51
  export const testLoginRoute = createRoute({
16
52
  method: 'post',
17
53
  path: '/test-login',
@@ -61,20 +97,23 @@ export const testLoginHandler: RouteHandler<typeof testLoginRoute, HonoEnv> = as
61
97
  }
62
98
 
63
99
  const { email, name } = c.req.valid('json')
64
- const db = c.get('db')
100
+ const db = c.env.DB ?? c.get('db')
65
101
 
66
102
  if (!db) {
67
103
  throw new Error('Database not initialized')
68
104
  }
69
105
 
70
106
  // Find or create user
71
- const existingUsers = await db
72
- .select()
73
- .from(users)
74
- .where(eq(users.email, email))
75
- .limit(1)
107
+ const existingUser = await queryOne(
108
+ db,
109
+ `SELECT ${USER_SELECT_COLUMNS}
110
+ FROM users
111
+ WHERE email = ? AND deleted_at IS NULL
112
+ LIMIT 1`,
113
+ [email]
114
+ )
76
115
 
77
- let user: UserRecord | undefined = existingUsers.at(0)
116
+ let user: UserRecord | undefined = existingUser ? mapUserRow(existingUser) : undefined
78
117
  let defaultAccountId: string | null = null
79
118
 
80
119
  if (user === undefined) {
@@ -82,49 +121,64 @@ export const testLoginHandler: RouteHandler<typeof testLoginRoute, HonoEnv> = as
82
121
  const userId = crypto.randomUUID()
83
122
  const now = new Date().toISOString()
84
123
 
85
- await db.insert(users).values({
86
- id: userId,
87
- email,
88
- name: name ?? 'E2E Test User',
89
- googleId: `test-${userId}`,
90
- status: 'active',
91
- createdAt: now,
92
- updatedAt: now,
93
- })
94
-
95
- const createdUsers = await db
96
- .select()
97
- .from(users)
98
- .where(eq(users.id, userId))
99
- .limit(1)
100
-
101
- user = createdUsers.at(0)
124
+ await execute(
125
+ db,
126
+ `INSERT INTO users (
127
+ id,
128
+ email,
129
+ name,
130
+ google_id,
131
+ status,
132
+ is_super_admin,
133
+ created_at,
134
+ updated_at
135
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
136
+ [
137
+ userId,
138
+ email,
139
+ name ?? 'E2E Test User',
140
+ `test-${userId}`,
141
+ 'active',
142
+ 0,
143
+ now,
144
+ now,
145
+ ]
146
+ )
147
+
148
+ const createdUser = await queryOne(
149
+ db,
150
+ `SELECT ${USER_SELECT_COLUMNS}
151
+ FROM users
152
+ WHERE id = ?
153
+ LIMIT 1`,
154
+ [userId]
155
+ )
156
+
157
+ user = createdUser ? mapUserRow(createdUser) : undefined
102
158
 
103
159
  // Create a default account for the user
104
160
  defaultAccountId = crypto.randomUUID()
105
- await db.insert(accounts).values({
106
- id: defaultAccountId,
107
- name: `${name ?? 'Test'}'s Workspace`,
108
- createdAt: now,
109
- updatedAt: now,
110
- })
161
+ await execute(
162
+ db,
163
+ `INSERT INTO accounts (id, name, created_at, updated_at) VALUES (?, ?, ?, ?)`,
164
+ [defaultAccountId, `${name ?? 'Test'}'s Workspace`, now, now]
165
+ )
111
166
 
112
167
  // Link user to account as ADMIN
113
- await db.insert(userAccounts).values({
114
- userId,
115
- accountId: defaultAccountId,
116
- role: 'ADMIN',
117
- })
168
+ await execute(
169
+ db,
170
+ `INSERT INTO user_accounts (user_id, account_id, role) VALUES (?, ?, ?)`,
171
+ [userId, defaultAccountId, 'ADMIN']
172
+ )
118
173
  } else {
119
174
  // Get the user's first account
120
- const userAccountResults = await db
121
- .select()
122
- .from(userAccounts)
123
- .where(eq(userAccounts.userId, user.id))
124
- .limit(1)
125
-
126
- const userAccount = userAccountResults.at(0)
127
- if (userAccount !== undefined) {
175
+ const userAccount = await queryOne<{ accountId: string }>(
176
+ db,
177
+ `SELECT account_id as accountId FROM user_accounts WHERE user_id = ? LIMIT 1`,
178
+ [user.id]
179
+ )
180
+
181
+ if (userAccount?.accountId) {
128
182
  defaultAccountId = userAccount.accountId
129
183
  }
130
184
  }