create-nexwire 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/README.md +10 -5
  2. package/express/src/framework/facade.ts +1 -0
  3. package/express/src/framework/http/logger.ts +13 -4
  4. package/express/src/framework/maker-cli/runtime/core.mjs +1 -2
  5. package/express/src/framework/notification/channels/mail.ts +8 -6
  6. package/express/src/middlewares/auth-middleware.ts +1 -2
  7. package/express/src/middlewares/role-middleware.ts +3 -13
  8. package/express/src/modules/auth/routes/index.ts +1 -2
  9. package/express/src/providers/notification.ts +2 -2
  10. package/express/src/resources/src/components/Switch.vue +60 -65
  11. package/express/src/resources/src/pages/auth/forgetPassword.vue +23 -30
  12. package/express/src/resources/src/pages/auth/login.vue +25 -28
  13. package/express/src/resources/src/pages/auth/register.vue +35 -39
  14. package/express/src/resources/src/pages/auth/resetPassword.vue +29 -37
  15. package/express/src/resources/src/pages/auth/verifyEmail.vue +15 -19
  16. package/express/src/resources/src/plugins/gum.ts +519 -203
  17. package/express/src/resources/src/stores/auth.ts +9 -6
  18. package/express/src/storage/database/nexwire.sqlite +0 -0
  19. package/fastify/.dockerignore +14 -0
  20. package/fastify/.env +44 -0
  21. package/fastify/.env.example +42 -0
  22. package/fastify/.vscode/extensions.json +6 -0
  23. package/fastify/.vscode/settings.json +37 -0
  24. package/fastify/LICENSE +21 -0
  25. package/fastify/README.md +232 -0
  26. package/fastify/biome.json +99 -0
  27. package/fastify/drizzle.config.ts +25 -0
  28. package/fastify/gitignore-stub +31 -0
  29. package/fastify/package.json +130 -0
  30. package/fastify/public/.gitkeep +0 -0
  31. package/fastify/src/config/app.ts +41 -0
  32. package/fastify/src/config/auth.ts +12 -0
  33. package/fastify/src/config/cache.ts +16 -0
  34. package/fastify/src/config/cookie.ts +15 -0
  35. package/fastify/src/config/cors.ts +12 -0
  36. package/fastify/src/config/database.ts +48 -0
  37. package/fastify/src/config/index.ts +83 -0
  38. package/fastify/src/config/jwt.ts +19 -0
  39. package/fastify/src/config/logging.ts +13 -0
  40. package/fastify/src/config/mail.ts +24 -0
  41. package/fastify/src/config/openapi.ts +36 -0
  42. package/fastify/src/config/queue.ts +30 -0
  43. package/fastify/src/config/rateLimit.ts +17 -0
  44. package/fastify/src/config/realtime.ts +15 -0
  45. package/fastify/src/config/redis.ts +18 -0
  46. package/fastify/src/config/security.ts +44 -0
  47. package/fastify/src/config/session.ts +17 -0
  48. package/fastify/src/config/storage.ts +22 -0
  49. package/fastify/src/config/validate.ts +50 -0
  50. package/fastify/src/database/migrations/sqlite/0000_init.sql +48 -0
  51. package/fastify/src/database/migrations/sqlite/meta/0000_snapshot.json +324 -0
  52. package/fastify/src/database/migrations/sqlite/meta/_journal.json +13 -0
  53. package/fastify/src/database/schema.ts +2 -0
  54. package/fastify/src/env.ts +76 -0
  55. package/fastify/src/framework/cache/cache.ts +114 -0
  56. package/fastify/src/framework/circuit-breaker/cb.ts +88 -0
  57. package/fastify/src/framework/database/app-schema.ts +37 -0
  58. package/fastify/src/framework/database/connection.ts +223 -0
  59. package/fastify/src/framework/database/migrate-hooks.ts +87 -0
  60. package/fastify/src/framework/database/paginate.ts +226 -0
  61. package/fastify/src/framework/database/schema.ts +20 -0
  62. package/fastify/src/framework/database/seed.ts +152 -0
  63. package/fastify/src/framework/events/dispatcher.ts +61 -0
  64. package/fastify/src/framework/facade.ts +96 -0
  65. package/fastify/src/framework/http/app.ts +176 -0
  66. package/fastify/src/framework/http/cors.ts +13 -0
  67. package/fastify/src/framework/http/fastify.d.ts +13 -0
  68. package/fastify/src/framework/http/logger.ts +110 -0
  69. package/fastify/src/framework/http/metrics.ts +22 -0
  70. package/fastify/src/framework/http/openapi.ts +48 -0
  71. package/fastify/src/framework/http/ratelimiter.ts +47 -0
  72. package/fastify/src/framework/http/security.ts +25 -0
  73. package/fastify/src/framework/http/static.ts +83 -0
  74. package/fastify/src/framework/http/validation.ts +49 -0
  75. package/fastify/src/framework/kernel.ts +43 -0
  76. package/fastify/src/framework/maker-cli/db/core.mjs +291 -0
  77. package/fastify/src/framework/maker-cli/db/index.mjs +204 -0
  78. package/fastify/src/framework/maker-cli/deploy/core.mjs +1046 -0
  79. package/fastify/src/framework/maker-cli/deploy/index.mjs +91 -0
  80. package/fastify/src/framework/maker-cli/index.mjs +65 -0
  81. package/fastify/src/framework/maker-cli/middleware/core.mjs +41 -0
  82. package/fastify/src/framework/maker-cli/middleware/index.mjs +17 -0
  83. package/fastify/src/framework/maker-cli/module/core.mjs +1066 -0
  84. package/fastify/src/framework/maker-cli/module/index.mjs +211 -0
  85. package/fastify/src/framework/maker-cli/provider/core.mjs +49 -0
  86. package/fastify/src/framework/maker-cli/provider/index.mjs +12 -0
  87. package/fastify/src/framework/maker-cli/runtime/build-ui.mjs +16 -0
  88. package/fastify/src/framework/maker-cli/runtime/core.mjs +560 -0
  89. package/fastify/src/framework/maker-cli/runtime/index.mjs +120 -0
  90. package/fastify/src/framework/maker-cli/runtime/redis-commander-patch.cjs +20 -0
  91. package/fastify/src/framework/maker-cli/stubs/controller/openapi.ts.stub +74 -0
  92. package/fastify/src/framework/maker-cli/stubs/controller/plain.ts.stub +77 -0
  93. package/fastify/src/framework/maker-cli/stubs/controller/standalone.ts.stub +51 -0
  94. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.bun.stub +52 -0
  95. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.pnpm.stub +55 -0
  96. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.stub +51 -0
  97. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.yarn.stub +55 -0
  98. package/fastify/src/framework/maker-cli/stubs/deploy/README.stub +59 -0
  99. package/fastify/src/framework/maker-cli/stubs/deploy/compose/mysql.server.stub +45 -0
  100. package/fastify/src/framework/maker-cli/stubs/deploy/compose/postgres.server.stub +45 -0
  101. package/fastify/src/framework/maker-cli/stubs/deploy/compose/sqlite.stub +45 -0
  102. package/fastify/src/framework/maker-cli/stubs/deploy/env/mysql.server.stub +68 -0
  103. package/fastify/src/framework/maker-cli/stubs/deploy/env/postgres.server.stub +68 -0
  104. package/fastify/src/framework/maker-cli/stubs/deploy/env/sqlite.stub +67 -0
  105. package/fastify/src/framework/maker-cli/stubs/deploy/scripts/auto-migrate.sh.stub +41 -0
  106. package/fastify/src/framework/maker-cli/stubs/deploy/server/README.stub +78 -0
  107. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/noredis.stub +120 -0
  108. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.dev.stub +133 -0
  109. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.stub +131 -0
  110. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/local.example.stub +11 -0
  111. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/noredis.stub +25 -0
  112. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/redis.stub +25 -0
  113. package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/README.stub +16 -0
  114. package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/app.example.com.stub +13 -0
  115. package/fastify/src/framework/maker-cli/stubs/deploy/server/pgadmin/servers.stub +14 -0
  116. package/fastify/src/framework/maker-cli/stubs/deploy/server/redis/redis.conf.stub +7 -0
  117. package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/noredis.stub +54 -0
  118. package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/redis.stub +70 -0
  119. package/fastify/src/framework/maker-cli/stubs/deploy/workflow/local.json.stub +25 -0
  120. package/fastify/src/framework/maker-cli/stubs/deploy/workflow/remote.json.stub +24 -0
  121. package/fastify/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
  122. package/fastify/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
  123. package/fastify/src/framework/maker-cli/stubs/job/name.ts.stub +25 -0
  124. package/fastify/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
  125. package/fastify/src/framework/maker-cli/stubs/middleware/name.ts.stub +15 -0
  126. package/fastify/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +10 -0
  127. package/fastify/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +10 -0
  128. package/fastify/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +10 -0
  129. package/fastify/src/framework/maker-cli/stubs/notification/controller.ts.stub +79 -0
  130. package/fastify/src/framework/maker-cli/stubs/notification/job.ts.stub +11 -0
  131. package/fastify/src/framework/maker-cli/stubs/notification/model.ts.stub +29 -0
  132. package/fastify/src/framework/maker-cli/stubs/notification/provider.ts.stub +174 -0
  133. package/fastify/src/framework/maker-cli/stubs/notification/route.api.ts.stub +71 -0
  134. package/fastify/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +15 -0
  135. package/fastify/src/framework/maker-cli/stubs/notification/schema.plain.ts.stub +26 -0
  136. package/fastify/src/framework/maker-cli/stubs/notification/schema.ts.stub +26 -0
  137. package/fastify/src/framework/maker-cli/stubs/provider/module-barrel.ts.stub +19 -0
  138. package/fastify/src/framework/maker-cli/stubs/provider/module.ts.stub +21 -0
  139. package/fastify/src/framework/maker-cli/stubs/provider/name.ts.stub +21 -0
  140. package/fastify/src/framework/maker-cli/stubs/route/api.ts.stub +101 -0
  141. package/fastify/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -0
  142. package/fastify/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +120 -0
  143. package/fastify/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +50 -0
  144. package/fastify/src/framework/maker-cli/stubs/schedule/name.ts.stub +36 -0
  145. package/fastify/src/framework/maker-cli/stubs/schema/name.plain.ts.stub +14 -0
  146. package/fastify/src/framework/maker-cli/stubs/schema/name.ts.stub +33 -0
  147. package/fastify/src/framework/maker-cli/stubs/seeder/name.ts.stub +18 -0
  148. package/fastify/src/framework/maker-cli/stubs/seeder/standalone.ts.stub +22 -0
  149. package/fastify/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
  150. package/fastify/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
  151. package/fastify/src/framework/maker-cli/stubs/test/unit.ts.stub +22 -0
  152. package/fastify/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
  153. package/fastify/src/framework/maker-cli/utils/env-config.mjs +44 -0
  154. package/fastify/src/framework/maker-cli/utils/env-db.mjs +61 -0
  155. package/fastify/src/framework/maker-cli/utils/file-ops.mjs +30 -0
  156. package/fastify/src/framework/maker-cli/utils/flags.mjs +14 -0
  157. package/fastify/src/framework/maker-cli/utils/help.mjs +379 -0
  158. package/fastify/src/framework/maker-cli/utils/naming.mjs +119 -0
  159. package/fastify/src/framework/maker-cli/utils/process.mjs +52 -0
  160. package/fastify/src/framework/maker-cli/utils/project-commands.mjs +201 -0
  161. package/fastify/src/framework/modules/discover.ts +57 -0
  162. package/fastify/src/framework/modules/routes.ts +31 -0
  163. package/fastify/src/framework/notification/channels/broadcast.ts +15 -0
  164. package/fastify/src/framework/notification/channels/mail.ts +42 -0
  165. package/fastify/src/framework/notification/contracts.ts +29 -0
  166. package/fastify/src/framework/notification/index.ts +59 -0
  167. package/fastify/src/framework/notification/registry.ts +15 -0
  168. package/fastify/src/framework/providers/adapter.ts +149 -0
  169. package/fastify/src/framework/providers/contracts.ts +126 -0
  170. package/fastify/src/framework/providers/loader.ts +105 -0
  171. package/fastify/src/framework/providers/registry.ts +88 -0
  172. package/fastify/src/framework/queue/clear.ts +22 -0
  173. package/fastify/src/framework/queue/queue.ts +401 -0
  174. package/fastify/src/framework/queue/ui.ts +194 -0
  175. package/fastify/src/framework/queue/worker.ts +61 -0
  176. package/fastify/src/framework/realtime/broadcast.ts +70 -0
  177. package/fastify/src/framework/realtime/index.ts +2 -0
  178. package/fastify/src/framework/realtime/socket-cookie.ts +72 -0
  179. package/fastify/src/framework/realtime/socket.ts +149 -0
  180. package/fastify/src/framework/realtime/types.ts +6 -0
  181. package/fastify/src/framework/realtime/ui.ts +16 -0
  182. package/fastify/src/framework/redis/client.ts +148 -0
  183. package/fastify/src/framework/runtime/runtime.ts +23 -0
  184. package/fastify/src/framework/runtime/server-adapter.ts +33 -0
  185. package/fastify/src/framework/scheduler/lock.ts +128 -0
  186. package/fastify/src/framework/scheduler/run.ts +48 -0
  187. package/fastify/src/framework/scheduler/scheduler.ts +155 -0
  188. package/fastify/src/framework/server.ts +162 -0
  189. package/fastify/src/framework/session/session.ts +160 -0
  190. package/fastify/src/framework/storage/storage.ts +744 -0
  191. package/fastify/src/framework/support/cookie.ts +71 -0
  192. package/fastify/src/framework/support/jwt.ts +33 -0
  193. package/fastify/src/framework/support/lifecycle.ts +35 -0
  194. package/fastify/src/framework/support/logger.ts +148 -0
  195. package/fastify/src/framework/support/mail.ts +56 -0
  196. package/fastify/src/framework/support/password.ts +23 -0
  197. package/fastify/src/framework/support/status-codes.ts +75 -0
  198. package/fastify/src/framework/support/upload.ts +181 -0
  199. package/fastify/src/framework/support/url.ts +41 -0
  200. package/fastify/src/middlewares/auth-middleware.ts +134 -0
  201. package/fastify/src/middlewares/role-middleware.ts +19 -0
  202. package/fastify/src/modules/auth/__tests__/forgetpass.test.ts +200 -0
  203. package/fastify/src/modules/auth/__tests__/login.test.ts +171 -0
  204. package/fastify/src/modules/auth/__tests__/logout.test.ts +60 -0
  205. package/fastify/src/modules/auth/__tests__/logoutalldevices.test.ts +49 -0
  206. package/fastify/src/modules/auth/__tests__/me.test.ts +91 -0
  207. package/fastify/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
  208. package/fastify/src/modules/auth/__tests__/register.test.ts +291 -0
  209. package/fastify/src/modules/auth/__tests__/resetpass.test.ts +206 -0
  210. package/fastify/src/modules/auth/__tests__/verifyemail.test.ts +183 -0
  211. package/fastify/src/modules/auth/controllers/auth.ts +252 -0
  212. package/fastify/src/modules/auth/controllers/role.ts +35 -0
  213. package/fastify/src/modules/auth/database/models/role.ts +14 -0
  214. package/fastify/src/modules/auth/database/models/user.ts +46 -0
  215. package/fastify/src/modules/auth/database/seeders/role.ts +18 -0
  216. package/fastify/src/modules/auth/database/seeders/user.ts +35 -0
  217. package/fastify/src/modules/auth/facade.ts +12 -0
  218. package/fastify/src/modules/auth/helpers/auth.ts +115 -0
  219. package/fastify/src/modules/auth/jobs/forgetpass.ts +20 -0
  220. package/fastify/src/modules/auth/jobs/registeruser.ts +23 -0
  221. package/fastify/src/modules/auth/jobs/verifyemail.ts +18 -0
  222. package/fastify/src/modules/auth/routes/index.ts +185 -0
  223. package/fastify/src/modules/auth/routes/role.ts +56 -0
  224. package/fastify/src/modules/auth/schemas/auth.ts +116 -0
  225. package/fastify/src/modules/auth/services/forgetpassword.ts +66 -0
  226. package/fastify/src/modules/auth/services/login.ts +63 -0
  227. package/fastify/src/modules/auth/services/logout.ts +25 -0
  228. package/fastify/src/modules/auth/services/logoutalldevices.ts +19 -0
  229. package/fastify/src/modules/auth/services/me.ts +31 -0
  230. package/fastify/src/modules/auth/services/refreshtoken.ts +110 -0
  231. package/fastify/src/modules/auth/services/register.ts +141 -0
  232. package/fastify/src/modules/auth/services/resetpassword.ts +69 -0
  233. package/fastify/src/modules/auth/services/verifyemail.ts +77 -0
  234. package/fastify/src/modules/auth/types/auth.ts +31 -0
  235. package/fastify/src/providers/README.md +107 -0
  236. package/fastify/src/providers/notification.ts +4 -0
  237. package/fastify/src/resources/index.html +17 -0
  238. package/fastify/src/resources/src/App.vue +5 -0
  239. package/fastify/src/resources/src/assets/css/styles.css +14761 -0
  240. package/fastify/src/resources/src/assets/css/styles.css.map +1 -0
  241. package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin-ext.woff2 +0 -0
  242. package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin.woff2 +0 -0
  243. package/fastify/src/resources/src/assets/images/favicon/favicon.ico +0 -0
  244. package/fastify/src/resources/src/assets/images/logo.png +0 -0
  245. package/fastify/src/resources/src/assets/scss/custom.css +217 -0
  246. package/fastify/src/resources/src/assets/scss/custom.css.map +1 -0
  247. package/fastify/src/resources/src/assets/scss/custom.scss +1346 -0
  248. package/fastify/src/resources/src/components/Button.vue +32 -0
  249. package/fastify/src/resources/src/components/Checkbox.vue +32 -0
  250. package/fastify/src/resources/src/components/Datepicker.vue +106 -0
  251. package/fastify/src/resources/src/components/FeatureButton.vue +53 -0
  252. package/fastify/src/resources/src/components/FloatButton.vue +33 -0
  253. package/fastify/src/resources/src/components/Href.vue +32 -0
  254. package/fastify/src/resources/src/components/Input.vue +243 -0
  255. package/fastify/src/resources/src/components/InputGroup.vue +147 -0
  256. package/fastify/src/resources/src/components/InputPasswordToggle.vue +229 -0
  257. package/fastify/src/resources/src/components/Modal.vue +71 -0
  258. package/fastify/src/resources/src/components/Pagebar.vue +26 -0
  259. package/fastify/src/resources/src/components/Refresh.vue +19 -0
  260. package/fastify/src/resources/src/components/Select.vue +384 -0
  261. package/fastify/src/resources/src/components/Spinner.vue +42 -0
  262. package/fastify/src/resources/src/components/Switch.vue +60 -0
  263. package/fastify/src/resources/src/components/TextArea.vue +117 -0
  264. package/fastify/src/resources/src/components/Toast.vue +56 -0
  265. package/fastify/src/resources/src/components/datatable/DataTableSkeleton.vue +98 -0
  266. package/fastify/src/resources/src/components/datatable/Pagination.vue +147 -0
  267. package/fastify/src/resources/src/components/datatable/SelectOpption.vue +64 -0
  268. package/fastify/src/resources/src/components/datatable/index.vue +199 -0
  269. package/fastify/src/resources/src/composables/useAuth.ts +52 -0
  270. package/fastify/src/resources/src/helpers/assets.ts +51 -0
  271. package/fastify/src/resources/src/helpers/nformatter.ts +10 -0
  272. package/fastify/src/resources/src/helpers/utils.ts +75 -0
  273. package/fastify/src/resources/src/layouts/AuthLayout.vue +20 -0
  274. package/fastify/src/resources/src/layouts/Layout/Footer.vue +23 -0
  275. package/fastify/src/resources/src/layouts/Layout/Header.vue +124 -0
  276. package/fastify/src/resources/src/layouts/Layout/Sidebar.vue +137 -0
  277. package/fastify/src/resources/src/layouts/Layout/index.vue +87 -0
  278. package/fastify/src/resources/src/main.ts +28 -0
  279. package/fastify/src/resources/src/pages/auth/forgetPassword.vue +76 -0
  280. package/fastify/src/resources/src/pages/auth/login.vue +96 -0
  281. package/fastify/src/resources/src/pages/auth/register.vue +129 -0
  282. package/fastify/src/resources/src/pages/auth/resetPassword.vue +110 -0
  283. package/fastify/src/resources/src/pages/auth/verifyEmail.vue +64 -0
  284. package/fastify/src/resources/src/pages/dashboard/index.vue +66 -0
  285. package/fastify/src/resources/src/plugins/axios.ts +31 -0
  286. package/fastify/src/resources/src/plugins/browserDetect.ts +55 -0
  287. package/fastify/src/resources/src/plugins/dialog.ts +216 -0
  288. package/fastify/src/resources/src/plugins/gum.ts +683 -0
  289. package/fastify/src/resources/src/plugins/pulse.ts +157 -0
  290. package/fastify/src/resources/src/plugins/routeProgress.ts +87 -0
  291. package/fastify/src/resources/src/router/index.ts +85 -0
  292. package/fastify/src/resources/src/stores/admin-ui.ts +148 -0
  293. package/fastify/src/resources/src/stores/auth.ts +187 -0
  294. package/fastify/src/resources/tsconfig.json +19 -0
  295. package/fastify/src/resources/vite.config.ts +64 -0
  296. package/fastify/src/storage/app/private/.gitkeep +0 -0
  297. package/fastify/src/storage/app/public/.gitkeep +0 -0
  298. package/fastify/src/storage/app/tmp/generated/.gitkeep +0 -0
  299. package/fastify/src/storage/database/nexwire.sqlite +0 -0
  300. package/fastify/src/storage/logs/.gitkeep +0 -0
  301. package/fastify/src/storage/logs/app.log +0 -0
  302. package/fastify/src/storage/logs/fatal.log +0 -0
  303. package/fastify/src/storage/tmp/.gitkeep +0 -0
  304. package/fastify/src/storage/trash/modules/.gitkeep +0 -0
  305. package/fastify/src/types/global.d.ts +32 -0
  306. package/fastify/tsconfig.json +19 -0
  307. package/fastify/vitest.config.ts +38 -0
  308. package/hono/src/framework/facade.ts +1 -0
  309. package/hono/src/framework/http/logger.ts +86 -81
  310. package/hono/src/framework/maker-cli/runtime/core.mjs +1 -2
  311. package/hono/src/framework/notification/channels/mail.ts +8 -6
  312. package/hono/src/middlewares/auth-middleware.ts +1 -2
  313. package/hono/src/middlewares/role-middleware.ts +3 -13
  314. package/hono/src/modules/auth/routes/index.ts +1 -2
  315. package/hono/src/providers/notification.ts +2 -2
  316. package/hono/src/resources/src/components/Switch.vue +60 -65
  317. package/hono/src/resources/src/pages/auth/forgetPassword.vue +23 -29
  318. package/hono/src/resources/src/pages/auth/login.vue +26 -29
  319. package/hono/src/resources/src/pages/auth/register.vue +36 -40
  320. package/hono/src/resources/src/pages/auth/resetPassword.vue +29 -37
  321. package/hono/src/resources/src/pages/auth/verifyEmail.vue +15 -19
  322. package/hono/src/resources/src/plugins/gum.ts +519 -203
  323. package/hono/src/resources/src/stores/auth.ts +9 -6
  324. package/hono/src/storage/database/nexwire.sqlite +0 -0
  325. package/package.json +3 -2
  326. package/src/index.js +11 -8
@@ -0,0 +1,206 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ vi.mock("@/framework/facade.js", () => ({
4
+ db: {
5
+ query: { users: { findFirst: vi.fn() } },
6
+ transaction: vi.fn()
7
+ },
8
+ password: { hashPassword: vi.fn() }
9
+ }));
10
+
11
+ vi.mock("@/modules/auth/helpers/auth.js", () => ({
12
+ hashResetToken: vi.fn((t: string) => `hashed:${t}`)
13
+ }));
14
+
15
+ vi.mock("@/modules/auth/database/models/user.js", () => ({
16
+ users: { __table: "users", id: "users.id", email: "users.email" },
17
+ passwordResetTokens: {
18
+ __table: "passwordResetTokens",
19
+ email: "passwordResetTokens.email",
20
+ token: "passwordResetTokens.token",
21
+ expiresAt: "passwordResetTokens.expiresAt"
22
+ },
23
+ refreshTokens: { __table: "refreshTokens", userId: "refreshTokens.userId" }
24
+ }));
25
+
26
+ import { db, password } from "@/framework/facade.js";
27
+ import { hashResetToken } from "@/modules/auth/helpers/auth.js";
28
+ import { resetPasswordService } from "@/modules/auth/services/resetpassword.js";
29
+
30
+ const fakeUser = { id: 1, email: "user@example.com", name: "Jane" };
31
+ const body = {
32
+ email: "user@example.com",
33
+ token: "plain-token",
34
+ password: "new-secret"
35
+ };
36
+
37
+ function buildTx({ consumeRow = { id: 1 } as unknown, failOnUpdate }: { consumeRow?: unknown; failOnUpdate?: 1 | 2 } = {}) {
38
+ const updateCalls: { table: unknown; values: unknown; where: unknown }[] = [];
39
+
40
+ const txDeleteReturning = vi.fn((_args?: unknown) => Promise.resolve(consumeRow ? [consumeRow] : []));
41
+ const txDeleteWhere = vi.fn((_where: unknown) => ({
42
+ returning: txDeleteReturning
43
+ }));
44
+ const txDelete = vi.fn((_table: unknown) => ({ where: txDeleteWhere }));
45
+
46
+ const txUpdate = vi.fn((table: unknown) => {
47
+ const callIndex = updateCalls.length + 1;
48
+ let currentValues: unknown;
49
+
50
+ const txUpdateWhere = vi.fn((where: unknown) => {
51
+ updateCalls.push({ table, values: currentValues, where });
52
+ if (failOnUpdate === callIndex) {
53
+ return Promise.reject(new Error("constraint violation"));
54
+ }
55
+ return Promise.resolve(undefined);
56
+ });
57
+
58
+ const txUpdateSet = vi.fn((values: unknown) => {
59
+ currentValues = values;
60
+ return { where: txUpdateWhere };
61
+ });
62
+
63
+ return { set: txUpdateSet };
64
+ });
65
+
66
+ return {
67
+ tx: { delete: txDelete, update: txUpdate },
68
+ txDelete,
69
+ txDeleteWhere,
70
+ txDeleteReturning,
71
+ txUpdate,
72
+ updateCalls
73
+ };
74
+ }
75
+
76
+ describe("resetPasswordService.resetPassword", () => {
77
+ beforeEach(() => {
78
+ vi.clearAllMocks();
79
+ vi.mocked(db.query.users.findFirst).mockResolvedValue(fakeUser as any);
80
+ vi.mocked(password.hashPassword).mockResolvedValue("hashed-new-pw" as any);
81
+ });
82
+
83
+ it("returns invalid_token without touching the DB when user does not exist", async () => {
84
+ vi.mocked(db.query.users.findFirst).mockResolvedValueOnce(undefined);
85
+
86
+ const result = await resetPasswordService.resetPassword(body);
87
+
88
+ expect(result).toEqual({
89
+ kind: "invalid_token",
90
+ message: "Invalid or expired reset token"
91
+ });
92
+ expect(db.transaction).not.toHaveBeenCalled();
93
+ expect(password.hashPassword).not.toHaveBeenCalled();
94
+ });
95
+
96
+ it("returns invalid_token when no reset token row was consumed", async () => {
97
+ const { tx } = buildTx({ consumeRow: null });
98
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(tx));
99
+
100
+ const result = await resetPasswordService.resetPassword(body);
101
+
102
+ expect(result).toEqual({
103
+ kind: "invalid_token",
104
+ message: "Invalid or expired reset token"
105
+ });
106
+ });
107
+
108
+ it("hashes the password BEFORE opening the transaction", async () => {
109
+ const callOrder: string[] = [];
110
+
111
+ vi.mocked(password.hashPassword).mockImplementationOnce(async () => {
112
+ callOrder.push("hashPassword");
113
+ return "hashed-new-pw" as any;
114
+ });
115
+
116
+ const { tx } = buildTx();
117
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => {
118
+ callOrder.push("transaction");
119
+ return fn(tx);
120
+ });
121
+
122
+ await resetPasswordService.resetPassword(body);
123
+
124
+ expect(callOrder).toEqual(["hashPassword", "transaction"]);
125
+ });
126
+
127
+ it("consumes the token by its HASHED value inside the transaction", async () => {
128
+ const { tx, txDelete, txDeleteWhere } = buildTx();
129
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(tx));
130
+
131
+ await resetPasswordService.resetPassword(body);
132
+
133
+ expect(hashResetToken).toHaveBeenCalledWith("plain-token");
134
+ expect(txDelete).toHaveBeenCalledTimes(1);
135
+
136
+ const whereArg = txDeleteWhere.mock.calls[0][0];
137
+ const serialized = JSON.stringify(whereArg);
138
+ expect(serialized).toContain("hashed:plain-token");
139
+ expect(serialized).not.toContain('"plain-token"');
140
+ });
141
+
142
+ it("updates password + revokes sessions inside the same transaction", async () => {
143
+ const { tx, updateCalls } = buildTx();
144
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(tx));
145
+
146
+ const result = await resetPasswordService.resetPassword(body);
147
+
148
+ expect(result).toEqual({
149
+ kind: "reset",
150
+ message: "Password reset successfully"
151
+ });
152
+
153
+ expect(updateCalls).toHaveLength(2);
154
+
155
+ expect((updateCalls[0].table as any).__table).toBe("users");
156
+ expect(updateCalls[0].values).toEqual({
157
+ password: "hashed-new-pw",
158
+ updatedAt: expect.any(Date)
159
+ });
160
+
161
+ expect((updateCalls[1].table as any).__table).toBe("refreshTokens");
162
+ expect(updateCalls[1].values).toEqual({ revoked: 1 });
163
+ });
164
+
165
+ it("returns invalid_token when the transaction fails before consuming", async () => {
166
+ vi.mocked(db.transaction).mockRejectedValueOnce(new Error("db down"));
167
+
168
+ const result = await resetPasswordService.resetPassword(body);
169
+
170
+ expect(result).toEqual({
171
+ kind: "invalid_token",
172
+ message: "Invalid or expired reset token"
173
+ });
174
+ });
175
+
176
+ it("stops at the failing update — second update is never attempted", async () => {
177
+ const { tx, updateCalls } = buildTx({ failOnUpdate: 1 });
178
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(tx));
179
+
180
+ const result = await resetPasswordService.resetPassword(body);
181
+
182
+ expect(result).toEqual({
183
+ kind: "invalid_token",
184
+ message: "Invalid or expired reset token"
185
+ });
186
+
187
+ expect(updateCalls).toHaveLength(1);
188
+ expect((updateCalls[0].table as any).__table).toBe("users");
189
+ expect(updateCalls.some((c) => (c.table as any).__table === "refreshTokens")).toBe(false);
190
+ });
191
+
192
+ it("propagates failure if the 2nd update rejects (session revocation fails)", async () => {
193
+ const { tx, updateCalls } = buildTx({ failOnUpdate: 2 });
194
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(tx));
195
+
196
+ const result = await resetPasswordService.resetPassword(body);
197
+
198
+ expect(result).toEqual({
199
+ kind: "invalid_token",
200
+ message: "Invalid or expired reset token"
201
+ });
202
+
203
+ expect(updateCalls).toHaveLength(2);
204
+ expect((updateCalls[1].table as any).__table).toBe("refreshTokens");
205
+ });
206
+ });
@@ -0,0 +1,183 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ vi.mock("@/config/index.js", () => ({
4
+ authConfig: { requireEmailVerification: true }
5
+ }));
6
+
7
+ vi.mock("@/framework/facade.js", () => ({
8
+ db: {
9
+ query: { users: { findFirst: vi.fn() } },
10
+ transaction: vi.fn()
11
+ }
12
+ }));
13
+
14
+ vi.mock("@/modules/auth/helpers/auth.js", () => ({
15
+ hashEmailVerificationToken: vi.fn((t: string) => `hashed:${t}`)
16
+ }));
17
+
18
+ vi.mock("@/modules/auth/database/models/user.js", () => ({
19
+ users: { __table: "users", id: "users.id", email: "users.email" },
20
+ emailVerificationTokens: {
21
+ __table: "emailVerificationTokens",
22
+ email: "emailVerificationTokens.email",
23
+ token: "emailVerificationTokens.token",
24
+ expiresAt: "emailVerificationTokens.expiresAt"
25
+ }
26
+ }));
27
+
28
+ import { authConfig } from "@/config/index.js";
29
+ import { db } from "@/framework/facade.js";
30
+ import { hashEmailVerificationToken } from "@/modules/auth/helpers/auth.js";
31
+ import { verifyEmailService } from "@/modules/auth/services/verifyemail.js";
32
+
33
+ const fakeUser = { id: 1, email: "user@example.com", name: "Jane" };
34
+ const body = { email: "user@example.com", token: "plain-verify-token" };
35
+
36
+ /** The exact result the service returns for every invalid-token path. */
37
+ const INVALID_TOKEN = {
38
+ kind: "invalid_token",
39
+ message: "Invalid or expired verification token"
40
+ } as const;
41
+
42
+ /**
43
+ * Build a fake tx.
44
+ * - consumeRow: row returned by delete().returning(), or null for "no match"
45
+ * - failOnUpdate: 1-indexed update that rejects (only one update here)
46
+ */
47
+ function buildTx({ consumeRow = { id: 1 } as unknown, failOnUpdate }: { consumeRow?: unknown; failOnUpdate?: 1 } = {}) {
48
+ const updateCalls: { table: unknown; values: unknown; where: unknown }[] = [];
49
+
50
+ const txDeleteReturning = vi.fn((_args?: unknown) => Promise.resolve(consumeRow ? [consumeRow] : []));
51
+ const txDeleteWhere = vi.fn((_where: unknown) => ({
52
+ returning: txDeleteReturning
53
+ }));
54
+ const txDelete = vi.fn((_table: unknown) => ({ where: txDeleteWhere }));
55
+
56
+ const txUpdate = vi.fn((table: unknown) => {
57
+ const callIndex = updateCalls.length + 1;
58
+ let currentValues: unknown;
59
+
60
+ const txUpdateWhere = vi.fn((where: unknown) => {
61
+ updateCalls.push({ table, values: currentValues, where });
62
+ if (failOnUpdate === callIndex) {
63
+ return Promise.reject(new Error("constraint violation"));
64
+ }
65
+ return Promise.resolve(undefined);
66
+ });
67
+
68
+ const txUpdateSet = vi.fn((values: unknown) => {
69
+ currentValues = values;
70
+ return { where: txUpdateWhere };
71
+ });
72
+
73
+ return { set: txUpdateSet };
74
+ });
75
+
76
+ return {
77
+ tx: { delete: txDelete, update: txUpdate },
78
+ txDelete,
79
+ txDeleteWhere,
80
+ txDeleteReturning,
81
+ txUpdate,
82
+ updateCalls
83
+ };
84
+ }
85
+
86
+ describe("verifyEmailService.verifyEmail", () => {
87
+ beforeEach(() => {
88
+ vi.clearAllMocks();
89
+ vi.mocked(authConfig).requireEmailVerification = true;
90
+ vi.mocked(db.query.users.findFirst).mockResolvedValue(fakeUser as any);
91
+ });
92
+
93
+ it("returns not_required without touching the DB when verification is disabled", async () => {
94
+ vi.mocked(authConfig).requireEmailVerification = false;
95
+
96
+ const result = await verifyEmailService.verifyEmail(body);
97
+
98
+ expect(result).toEqual({
99
+ kind: "not_required",
100
+ message: "Email verification is not required"
101
+ });
102
+ expect(db.query.users.findFirst).not.toHaveBeenCalled();
103
+ expect(db.transaction).not.toHaveBeenCalled();
104
+ });
105
+
106
+ it("returns invalid_token when the user does not exist", async () => {
107
+ vi.mocked(db.query.users.findFirst).mockResolvedValueOnce(undefined);
108
+
109
+ const result = await verifyEmailService.verifyEmail(body);
110
+
111
+ expect(result).toEqual(INVALID_TOKEN);
112
+ expect(db.transaction).not.toHaveBeenCalled();
113
+ });
114
+
115
+ it("returns invalid_token when no token row was consumed", async () => {
116
+ const { tx } = buildTx({ consumeRow: null });
117
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(tx));
118
+
119
+ const result = await verifyEmailService.verifyEmail(body);
120
+
121
+ expect(result).toEqual(INVALID_TOKEN);
122
+ });
123
+
124
+ it("consumes the token by its HASHED value inside the transaction", async () => {
125
+ const { tx, txDelete, txDeleteWhere } = buildTx();
126
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(tx));
127
+
128
+ await verifyEmailService.verifyEmail(body);
129
+
130
+ expect(hashEmailVerificationToken).toHaveBeenCalledWith("plain-verify-token");
131
+ expect(txDelete).toHaveBeenCalledTimes(1);
132
+
133
+ const whereArg = txDeleteWhere.mock.calls[0][0];
134
+ const serialized = JSON.stringify(whereArg);
135
+ expect(serialized).toContain("hashed:plain-verify-token");
136
+ expect(serialized).not.toContain('"plain-verify-token"');
137
+ });
138
+
139
+ it("marks the user verified by id (not email) inside the transaction", async () => {
140
+ const { tx, updateCalls } = buildTx();
141
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(tx));
142
+
143
+ const result = await verifyEmailService.verifyEmail(body);
144
+
145
+ expect(result).toEqual({
146
+ kind: "verified",
147
+ message: "Email verified successfully"
148
+ });
149
+
150
+ expect(updateCalls).toHaveLength(1);
151
+ expect((updateCalls[0].table as any).__table).toBe("users");
152
+
153
+ const values = updateCalls[0].values as {
154
+ emailVerifiedAt: Date;
155
+ updatedAt: Date;
156
+ };
157
+ expect(values.emailVerifiedAt).toBeInstanceOf(Date);
158
+ expect(values.updatedAt).toBeInstanceOf(Date);
159
+
160
+ // Same Date instance for both fields — proves `now` is shared
161
+ expect(values.emailVerifiedAt).toBe(values.updatedAt);
162
+ });
163
+
164
+ it("returns invalid_token when the transaction rejects", async () => {
165
+ vi.mocked(db.transaction).mockRejectedValueOnce(new Error("db down"));
166
+
167
+ const result = await verifyEmailService.verifyEmail(body);
168
+
169
+ expect(result).toEqual(INVALID_TOKEN);
170
+ });
171
+
172
+ it("returns invalid_token when the update rejects inside the transaction", async () => {
173
+ const { tx, updateCalls } = buildTx({ failOnUpdate: 1 });
174
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(tx));
175
+
176
+ const result = await verifyEmailService.verifyEmail(body);
177
+
178
+ expect(result).toEqual(INVALID_TOKEN);
179
+
180
+ expect(updateCalls).toHaveLength(1);
181
+ expect((updateCalls[0].table as any).__table).toBe("users");
182
+ });
183
+ });
@@ -0,0 +1,252 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+ import { cookie, HttpStatusCodes } from "@/framework/facade.js";
3
+ import { registerService } from "@/modules/auth/services/register.js";
4
+ import type {
5
+ ForgotPasswordInput,
6
+ LoginInput,
7
+ RefreshTokenInput,
8
+ RegisterInput,
9
+ ResetPasswordInput,
10
+ VerifyEmailInput
11
+ } from "@/modules/auth/types/auth.js";
12
+ import { forgotPasswordService } from "../services/forgetpassword.js";
13
+ import { loginService } from "../services/login.js";
14
+ import { logoutService } from "../services/logout.js";
15
+ import { logoutAllDevicesService } from "../services/logoutalldevices.js";
16
+ import { meService } from "../services/me.js";
17
+ import { refreshTokenService } from "../services/refreshtoken.js";
18
+ import { resetPasswordService } from "../services/resetpassword.js";
19
+ import { verifyEmailService } from "../services/verifyemail.js";
20
+
21
+ type Body<T> = FastifyRequest<{ Body: T }>;
22
+
23
+ /**
24
+ * Why: Creates a new user, issues tokens, and triggers signup side effects.
25
+ * When: Used on first-time account creation.
26
+ * Where: POST auth register route.
27
+ */
28
+ export const register = async (request: Body<RegisterInput>, reply: FastifyReply) => {
29
+ try {
30
+ const result = await registerService.register(request, reply, request.body);
31
+
32
+ switch (result.kind) {
33
+ case "email_exists":
34
+ return reply.code(HttpStatusCodes.UNPROCESSABLE_ENTITY).send({ message: result.message });
35
+
36
+ case "verification_required":
37
+ return reply.code(HttpStatusCodes.CREATED).send({ message: result.message });
38
+
39
+ case "registered":
40
+ return reply.code(HttpStatusCodes.CREATED).send({
41
+ message: result.message,
42
+ data: result.data
43
+ });
44
+ }
45
+ } catch (error) {
46
+ console.error("Register error:", error);
47
+ return reply.code(HttpStatusCodes.INTERNAL_SERVER_ERROR).send({ message: "Failed to register user" });
48
+ }
49
+ };
50
+
51
+ /**
52
+ * Why: Authenticates user credentials and rotates active login cookies/tokens.
53
+ * When: Used whenever a user signs in.
54
+ * Where: POST auth login route.
55
+ */
56
+ export const login = async (request: Body<LoginInput>, reply: FastifyReply) => {
57
+ try {
58
+ const result = await loginService.login(request, reply, request.body);
59
+
60
+ switch (result.kind) {
61
+ case "invalid_credentials":
62
+ return reply.code(HttpStatusCodes.UNAUTHORIZED).send({ message: result.message });
63
+
64
+ case "email_not_verified":
65
+ return reply.code(HttpStatusCodes.FORBIDDEN).send({ message: result.message });
66
+
67
+ case "logged_in":
68
+ return reply.code(HttpStatusCodes.OK).send({
69
+ message: result.message,
70
+ data: result.data
71
+ });
72
+ }
73
+ } catch (error) {
74
+ console.error("Login error:", error);
75
+ return reply.code(HttpStatusCodes.INTERNAL_SERVER_ERROR).send({ message: "Failed to login" });
76
+ }
77
+ };
78
+
79
+ /**
80
+ * Why: Returns the currently authenticated user profile.
81
+ * When: Used by clients to bootstrap session/user state.
82
+ * Where: GET auth me route.
83
+ */
84
+ export const me = async (request: FastifyRequest, reply: FastifyReply) => {
85
+ try {
86
+ const auth = request.auth as { id: number };
87
+ const result = await meService.me(auth.id);
88
+
89
+ switch (result.kind) {
90
+ case "not_found":
91
+ return reply.code(HttpStatusCodes.NOT_FOUND).send({ message: result.message });
92
+
93
+ case "found":
94
+ return reply.code(HttpStatusCodes.OK).send({
95
+ message: result.message,
96
+ data: result.data
97
+ });
98
+ }
99
+ } catch (error) {
100
+ console.error("Me error:", error);
101
+ return reply.code(HttpStatusCodes.INTERNAL_SERVER_ERROR).send({ message: "Failed to fetch user" });
102
+ }
103
+ };
104
+
105
+ /**
106
+ * Why: Revokes current refresh token and clears auth cookies.
107
+ * When: Used when the active device logs out.
108
+ * Where: POST auth logout route.
109
+ */
110
+ export const logout = async (request: FastifyRequest, reply: FastifyReply) => {
111
+ try {
112
+ const result = await logoutService.logout(request, reply);
113
+
114
+ // cookie cleanup is an HTTP concern — stays in the controller
115
+ cookie.deleteAuth(reply);
116
+ cookie.deleteRefresh(reply);
117
+
118
+ return reply.code(HttpStatusCodes.OK).send({ message: result.message });
119
+ } catch (error) {
120
+ console.error("Logout error:", error);
121
+
122
+ cookie.deleteAuth(reply);
123
+ cookie.deleteRefresh(reply);
124
+
125
+ return reply.code(HttpStatusCodes.INTERNAL_SERVER_ERROR).send({ message: "Failed to logout" });
126
+ }
127
+ };
128
+
129
+ /**
130
+ * Why: Starts password reset flow and queues email delivery event.
131
+ * When: Used when user requests a forgot-password link.
132
+ * Where: POST auth forgot-password route.
133
+ */
134
+ export const forgotPassword = async (request: Body<ForgotPasswordInput>, reply: FastifyReply) => {
135
+ try {
136
+ const result = await forgotPasswordService.forgotPassword(request.body);
137
+
138
+ // Single success path — intentionally identical whether or not the email exists.
139
+ return reply.code(HttpStatusCodes.OK).send({ message: result.message });
140
+ } catch (error) {
141
+ console.error("Forgot password error:", error);
142
+ return reply
143
+ .code(HttpStatusCodes.INTERNAL_SERVER_ERROR)
144
+ .send({ message: "Failed to process forgot password request" });
145
+ }
146
+ };
147
+
148
+ /**
149
+ * Why: Verifies reset token and persists the new password.
150
+ * When: Used after user submits reset token + new password.
151
+ * Where: POST auth reset-password route.
152
+ */
153
+ export const resetPassword = async (request: Body<ResetPasswordInput>, reply: FastifyReply) => {
154
+ try {
155
+ const result = await resetPasswordService.resetPassword(request.body);
156
+
157
+ switch (result.kind) {
158
+ case "invalid_token":
159
+ return reply.code(HttpStatusCodes.UNPROCESSABLE_ENTITY).send({ message: result.message });
160
+
161
+ case "reset":
162
+ return reply.code(HttpStatusCodes.OK).send({ message: result.message });
163
+ }
164
+ } catch (error) {
165
+ console.error("Reset password error:", error);
166
+ return reply.code(HttpStatusCodes.INTERNAL_SERVER_ERROR).send({ message: "Failed to reset password" });
167
+ }
168
+ };
169
+
170
+ /**
171
+ * Why: Validates email verification token and marks user as verified.
172
+ * When: Used when user opens verification link from inbox.
173
+ * Where: POST auth verify-email route.
174
+ */
175
+ export const verifyEmail = async (request: Body<VerifyEmailInput>, reply: FastifyReply) => {
176
+ try {
177
+ const result = await verifyEmailService.verifyEmail(request.body);
178
+
179
+ switch (result.kind) {
180
+ case "not_required":
181
+ return reply.code(HttpStatusCodes.OK).send({ message: result.message });
182
+
183
+ case "invalid_token":
184
+ return reply.code(HttpStatusCodes.UNPROCESSABLE_ENTITY).send({ message: result.message });
185
+
186
+ case "verified":
187
+ return reply.code(HttpStatusCodes.OK).send({ message: result.message });
188
+ }
189
+ } catch (error) {
190
+ console.error("Verify email error:", error);
191
+ return reply.code(HttpStatusCodes.INTERNAL_SERVER_ERROR).send({ message: "Failed to verify email" });
192
+ }
193
+ };
194
+
195
+ /**
196
+ * Why: Rotates refresh token and reissues access credentials.
197
+ * When: Used when access token expires but refresh token is still valid.
198
+ * Where: POST auth refresh-token route.
199
+ */
200
+ export const refreshToken = async (request: Body<RefreshTokenInput>, reply: FastifyReply) => {
201
+ try {
202
+ const result = await refreshTokenService.refreshToken(request.body);
203
+
204
+ switch (result.kind) {
205
+ case "invalid_token":
206
+ case "revoked":
207
+ case "expired":
208
+ case "user_not_found":
209
+ return reply.code(HttpStatusCodes.UNAUTHORIZED).send({ message: result.message });
210
+
211
+ case "refreshed": {
212
+ // Cookie handling stays in the controller — it's an HTTP concern.
213
+ await cookie.setAuth(reply, result.cookies.accessToken);
214
+ await cookie.setRefresh(reply, result.cookies.refreshToken, result.cookies.refreshExpiry);
215
+
216
+ return reply.code(HttpStatusCodes.OK).send({
217
+ message: result.message,
218
+ data: result.data
219
+ });
220
+ }
221
+ }
222
+ } catch (error) {
223
+ console.error("Refresh token error:", error);
224
+ return reply.code(HttpStatusCodes.UNAUTHORIZED).send({ message: "Invalid or expired refresh token" });
225
+ }
226
+ };
227
+
228
+ /**
229
+ * Why: Revokes all refresh tokens for account-wide logout.
230
+ * When: Used for "logout from all devices" security action.
231
+ * Where: POST auth logout-all-devices route.
232
+ */
233
+ export const logoutAllDevices = async (request: FastifyRequest, reply: FastifyReply) => {
234
+ try {
235
+ const auth = request.auth as { id: number } | undefined | null;
236
+
237
+ if (!auth) {
238
+ return reply.code(HttpStatusCodes.UNAUTHORIZED).send({ message: "Unauthorized" });
239
+ }
240
+
241
+ const result = await logoutAllDevicesService.logoutAllDevices(auth.id);
242
+
243
+ // cookie cleanup is an HTTP concern — stays in the controller
244
+ cookie.deleteAuth(reply);
245
+ cookie.deleteRefresh(reply);
246
+
247
+ return reply.code(HttpStatusCodes.OK).send({ message: result.message });
248
+ } catch (error) {
249
+ console.error("Logout all devices error:", error);
250
+ return reply.code(HttpStatusCodes.INTERNAL_SERVER_ERROR).send({ message: "Failed to logout from all devices" });
251
+ }
252
+ };
@@ -0,0 +1,35 @@
1
+ import { eq } from "drizzle-orm";
2
+ import type { FastifyReply, FastifyRequest } from "fastify";
3
+ import { db, HttpStatusCodes } from "@/framework/facade.js";
4
+ import { roles } from "@/modules/auth/database/models/role.js";
5
+
6
+ function sanitizeRole(role: any) {
7
+ if (!role) return null;
8
+ return {
9
+ id: role.id,
10
+ name: role.name,
11
+ createdAt: role.createdAt ? String(role.createdAt) : null,
12
+ updatedAt: role.updatedAt ? String(role.updatedAt) : null
13
+ };
14
+ }
15
+
16
+ /**
17
+ * Why: Returns role records for role-aware UI and authorization setup.
18
+ * When: Used by admin/management screens that need available roles.
19
+ * Where: Mounted under auth role routes.
20
+ */
21
+ export const index = async (_request: FastifyRequest, reply: FastifyReply) => {
22
+ const result = await db.query.roles.findMany();
23
+ return reply.code(HttpStatusCodes.OK).send({ message: "Success", data: result.map(sanitizeRole) });
24
+ };
25
+
26
+ /**
27
+ * Why: Fetches a single role to inspect role metadata by id.
28
+ * When: Used for detail views or role validation checks.
29
+ * Where: Mounted on auth role detail route with path param id.
30
+ */
31
+ export const show = async (request: FastifyRequest<{ Params: { id: number } }>, reply: FastifyReply) => {
32
+ const { id } = request.params;
33
+ const result = await db.query.roles.findFirst({ where: eq(roles.id, Number(id)) });
34
+ return reply.code(HttpStatusCodes.OK).send({ message: "Success", data: sanitizeRole(result) });
35
+ };
@@ -0,0 +1,14 @@
1
+ import { relations } from "drizzle-orm";
2
+ import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
3
+ import { users } from "@/modules/auth/database/models/user.js";
4
+
5
+ export const roles = sqliteTable("roles", {
6
+ id: integer("id").primaryKey({ autoIncrement: true }),
7
+ name: text("name").notNull().unique(),
8
+ createdAt: integer("created_at", { mode: "timestamp" }).defaultNow().notNull(),
9
+ updatedAt: integer("updated_at", { mode: "timestamp" }).defaultNow().notNull()
10
+ });
11
+
12
+ export const rolesRelations = relations(roles, ({ many }) => ({
13
+ users: many(users)
14
+ }));