create-nexwire 4.2.5 → 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 (399) hide show
  1. package/README.md +29 -12
  2. package/express/.env +1 -1
  3. package/express/package.json +1 -1
  4. package/express/src/config/database.ts +48 -29
  5. package/express/src/config/index.ts +83 -83
  6. package/express/src/config/openapi.ts +6 -24
  7. package/express/src/config/queue.ts +30 -30
  8. package/express/src/config/validate.ts +50 -40
  9. package/express/src/database/migrations/sqlite/0000_init.sql +47 -47
  10. package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  11. package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
  12. package/express/src/framework/cache/cache.ts +40 -8
  13. package/express/src/framework/circuit-breaker/cb.ts +88 -60
  14. package/express/src/framework/database/connection.ts +52 -27
  15. package/express/src/framework/events/dispatcher.ts +61 -49
  16. package/express/src/framework/facade.ts +47 -3
  17. package/express/src/framework/http/app.ts +28 -22
  18. package/express/src/framework/http/logger.ts +37 -17
  19. package/express/src/framework/http/openapi.ts +9 -11
  20. package/express/src/framework/maker-cli/runtime/core.mjs +1 -2
  21. package/express/src/framework/modules/routes.ts +26 -21
  22. package/express/src/framework/notification/channels/broadcast.ts +15 -0
  23. package/express/src/framework/notification/channels/mail.ts +42 -0
  24. package/express/src/framework/notification/contracts.ts +29 -0
  25. package/express/src/framework/notification/index.ts +19 -94
  26. package/express/src/framework/notification/registry.ts +15 -0
  27. package/express/src/framework/providers/adapter.ts +27 -17
  28. package/express/src/framework/queue/queue.ts +43 -44
  29. package/express/src/framework/realtime/broadcast.ts +29 -5
  30. package/express/src/framework/realtime/socket.ts +149 -119
  31. package/express/src/framework/redis/client.ts +148 -151
  32. package/express/src/framework/runtime/server-adapter.ts +39 -34
  33. package/express/src/framework/scheduler/lock.ts +128 -116
  34. package/express/src/framework/server.ts +33 -49
  35. package/express/src/framework/session/session.ts +69 -14
  36. package/express/src/framework/storage/storage.ts +744 -785
  37. package/express/src/framework/support/mail.ts +15 -7
  38. package/express/src/middlewares/auth-middleware.ts +1 -2
  39. package/express/src/middlewares/role-middleware.ts +3 -13
  40. package/express/src/modules/auth/routes/index.ts +1 -2
  41. package/express/src/providers/notification.ts +4 -0
  42. package/express/src/resources/src/components/Switch.vue +60 -65
  43. package/express/src/resources/src/layouts/Layout/Header.vue +124 -105
  44. package/express/src/resources/src/pages/auth/forgetPassword.vue +23 -30
  45. package/express/src/resources/src/pages/auth/login.vue +25 -28
  46. package/express/src/resources/src/pages/auth/register.vue +35 -39
  47. package/express/src/resources/src/pages/auth/resetPassword.vue +29 -37
  48. package/express/src/resources/src/pages/auth/verifyEmail.vue +15 -19
  49. package/express/src/resources/src/plugins/gum.ts +519 -203
  50. package/express/src/resources/src/plugins/pulse.ts +157 -147
  51. package/express/src/resources/src/stores/auth.ts +9 -6
  52. package/express/src/resources/vite.config.ts +54 -43
  53. package/express/src/storage/database/nexwire.sqlite +0 -0
  54. package/express/src/storage/logs/app.log +0 -0
  55. package/express/src/storage/logs/fatal.log +0 -0
  56. package/express/src/types/global.d.ts +32 -26
  57. package/fastify/.dockerignore +14 -0
  58. package/fastify/.env +44 -0
  59. package/fastify/.env.example +42 -0
  60. package/fastify/.vscode/extensions.json +6 -0
  61. package/fastify/.vscode/settings.json +37 -0
  62. package/fastify/LICENSE +21 -0
  63. package/fastify/README.md +232 -0
  64. package/fastify/biome.json +99 -0
  65. package/fastify/drizzle.config.ts +25 -0
  66. package/fastify/gitignore-stub +31 -0
  67. package/fastify/package.json +130 -0
  68. package/fastify/public/.gitkeep +0 -0
  69. package/fastify/src/config/app.ts +41 -0
  70. package/fastify/src/config/auth.ts +12 -0
  71. package/fastify/src/config/cache.ts +16 -0
  72. package/fastify/src/config/cookie.ts +15 -0
  73. package/fastify/src/config/cors.ts +12 -0
  74. package/fastify/src/config/database.ts +48 -0
  75. package/fastify/src/config/index.ts +83 -0
  76. package/fastify/src/config/jwt.ts +19 -0
  77. package/fastify/src/config/logging.ts +13 -0
  78. package/fastify/src/config/mail.ts +24 -0
  79. package/fastify/src/config/openapi.ts +36 -0
  80. package/fastify/src/config/queue.ts +30 -0
  81. package/fastify/src/config/rateLimit.ts +17 -0
  82. package/fastify/src/config/realtime.ts +15 -0
  83. package/fastify/src/config/redis.ts +18 -0
  84. package/fastify/src/config/security.ts +44 -0
  85. package/fastify/src/config/session.ts +17 -0
  86. package/fastify/src/config/storage.ts +22 -0
  87. package/fastify/src/config/validate.ts +50 -0
  88. package/fastify/src/database/migrations/sqlite/0000_init.sql +48 -0
  89. package/fastify/src/database/migrations/sqlite/meta/0000_snapshot.json +324 -0
  90. package/fastify/src/database/migrations/sqlite/meta/_journal.json +13 -0
  91. package/fastify/src/database/schema.ts +2 -0
  92. package/fastify/src/env.ts +76 -0
  93. package/fastify/src/framework/cache/cache.ts +114 -0
  94. package/fastify/src/framework/circuit-breaker/cb.ts +88 -0
  95. package/fastify/src/framework/database/app-schema.ts +37 -0
  96. package/fastify/src/framework/database/connection.ts +223 -0
  97. package/fastify/src/framework/database/migrate-hooks.ts +87 -0
  98. package/fastify/src/framework/database/paginate.ts +226 -0
  99. package/fastify/src/framework/database/schema.ts +20 -0
  100. package/fastify/src/framework/database/seed.ts +152 -0
  101. package/fastify/src/framework/events/dispatcher.ts +61 -0
  102. package/fastify/src/framework/facade.ts +96 -0
  103. package/fastify/src/framework/http/app.ts +176 -0
  104. package/fastify/src/framework/http/cors.ts +13 -0
  105. package/fastify/src/framework/http/fastify.d.ts +13 -0
  106. package/fastify/src/framework/http/logger.ts +110 -0
  107. package/fastify/src/framework/http/metrics.ts +22 -0
  108. package/fastify/src/framework/http/openapi.ts +48 -0
  109. package/fastify/src/framework/http/ratelimiter.ts +47 -0
  110. package/fastify/src/framework/http/security.ts +25 -0
  111. package/fastify/src/framework/http/static.ts +83 -0
  112. package/fastify/src/framework/http/validation.ts +49 -0
  113. package/fastify/src/framework/kernel.ts +43 -0
  114. package/fastify/src/framework/maker-cli/db/core.mjs +291 -0
  115. package/fastify/src/framework/maker-cli/db/index.mjs +204 -0
  116. package/fastify/src/framework/maker-cli/deploy/core.mjs +1046 -0
  117. package/fastify/src/framework/maker-cli/deploy/index.mjs +91 -0
  118. package/fastify/src/framework/maker-cli/index.mjs +65 -0
  119. package/fastify/src/framework/maker-cli/middleware/core.mjs +41 -0
  120. package/fastify/src/framework/maker-cli/middleware/index.mjs +17 -0
  121. package/fastify/src/framework/maker-cli/module/core.mjs +1066 -0
  122. package/fastify/src/framework/maker-cli/module/index.mjs +211 -0
  123. package/fastify/src/framework/maker-cli/provider/core.mjs +49 -0
  124. package/fastify/src/framework/maker-cli/provider/index.mjs +12 -0
  125. package/fastify/src/framework/maker-cli/runtime/build-ui.mjs +16 -0
  126. package/fastify/src/framework/maker-cli/runtime/core.mjs +560 -0
  127. package/fastify/src/framework/maker-cli/runtime/index.mjs +120 -0
  128. package/fastify/src/framework/maker-cli/runtime/redis-commander-patch.cjs +20 -0
  129. package/fastify/src/framework/maker-cli/stubs/controller/openapi.ts.stub +74 -0
  130. package/fastify/src/framework/maker-cli/stubs/controller/plain.ts.stub +77 -0
  131. package/fastify/src/framework/maker-cli/stubs/controller/standalone.ts.stub +51 -0
  132. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.bun.stub +52 -0
  133. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.pnpm.stub +55 -0
  134. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.stub +51 -0
  135. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.yarn.stub +55 -0
  136. package/fastify/src/framework/maker-cli/stubs/deploy/README.stub +59 -0
  137. package/fastify/src/framework/maker-cli/stubs/deploy/compose/mysql.server.stub +45 -0
  138. package/fastify/src/framework/maker-cli/stubs/deploy/compose/postgres.server.stub +45 -0
  139. package/fastify/src/framework/maker-cli/stubs/deploy/compose/sqlite.stub +45 -0
  140. package/fastify/src/framework/maker-cli/stubs/deploy/env/mysql.server.stub +68 -0
  141. package/fastify/src/framework/maker-cli/stubs/deploy/env/postgres.server.stub +68 -0
  142. package/fastify/src/framework/maker-cli/stubs/deploy/env/sqlite.stub +67 -0
  143. package/fastify/src/framework/maker-cli/stubs/deploy/scripts/auto-migrate.sh.stub +41 -0
  144. package/fastify/src/framework/maker-cli/stubs/deploy/server/README.stub +78 -0
  145. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/noredis.stub +120 -0
  146. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.dev.stub +133 -0
  147. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.stub +131 -0
  148. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/local.example.stub +11 -0
  149. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/noredis.stub +25 -0
  150. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/redis.stub +25 -0
  151. package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/README.stub +16 -0
  152. package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/app.example.com.stub +13 -0
  153. package/fastify/src/framework/maker-cli/stubs/deploy/server/pgadmin/servers.stub +14 -0
  154. package/fastify/src/framework/maker-cli/stubs/deploy/server/redis/redis.conf.stub +7 -0
  155. package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/noredis.stub +54 -0
  156. package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/redis.stub +70 -0
  157. package/fastify/src/framework/maker-cli/stubs/deploy/workflow/local.json.stub +25 -0
  158. package/fastify/src/framework/maker-cli/stubs/deploy/workflow/remote.json.stub +24 -0
  159. package/fastify/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
  160. package/fastify/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
  161. package/fastify/src/framework/maker-cli/stubs/job/name.ts.stub +25 -0
  162. package/fastify/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
  163. package/fastify/src/framework/maker-cli/stubs/middleware/name.ts.stub +15 -0
  164. package/fastify/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +10 -0
  165. package/fastify/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +10 -0
  166. package/fastify/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +10 -0
  167. package/fastify/src/framework/maker-cli/stubs/notification/controller.ts.stub +79 -0
  168. package/fastify/src/framework/maker-cli/stubs/notification/job.ts.stub +11 -0
  169. package/fastify/src/framework/maker-cli/stubs/notification/model.ts.stub +29 -0
  170. package/fastify/src/framework/maker-cli/stubs/notification/provider.ts.stub +174 -0
  171. package/fastify/src/framework/maker-cli/stubs/notification/route.api.ts.stub +71 -0
  172. package/fastify/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +15 -0
  173. package/fastify/src/framework/maker-cli/stubs/notification/schema.plain.ts.stub +26 -0
  174. package/fastify/src/framework/maker-cli/stubs/notification/schema.ts.stub +26 -0
  175. package/fastify/src/framework/maker-cli/stubs/provider/module-barrel.ts.stub +19 -0
  176. package/fastify/src/framework/maker-cli/stubs/provider/module.ts.stub +21 -0
  177. package/fastify/src/framework/maker-cli/stubs/provider/name.ts.stub +21 -0
  178. package/fastify/src/framework/maker-cli/stubs/route/api.ts.stub +101 -0
  179. package/fastify/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -0
  180. package/fastify/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +120 -0
  181. package/fastify/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +50 -0
  182. package/fastify/src/framework/maker-cli/stubs/schedule/name.ts.stub +36 -0
  183. package/fastify/src/framework/maker-cli/stubs/schema/name.plain.ts.stub +14 -0
  184. package/fastify/src/framework/maker-cli/stubs/schema/name.ts.stub +33 -0
  185. package/fastify/src/framework/maker-cli/stubs/seeder/name.ts.stub +18 -0
  186. package/fastify/src/framework/maker-cli/stubs/seeder/standalone.ts.stub +22 -0
  187. package/fastify/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
  188. package/fastify/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
  189. package/fastify/src/framework/maker-cli/stubs/test/unit.ts.stub +22 -0
  190. package/fastify/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
  191. package/fastify/src/framework/maker-cli/utils/env-config.mjs +44 -0
  192. package/fastify/src/framework/maker-cli/utils/env-db.mjs +61 -0
  193. package/fastify/src/framework/maker-cli/utils/file-ops.mjs +30 -0
  194. package/fastify/src/framework/maker-cli/utils/flags.mjs +14 -0
  195. package/fastify/src/framework/maker-cli/utils/help.mjs +379 -0
  196. package/fastify/src/framework/maker-cli/utils/naming.mjs +119 -0
  197. package/fastify/src/framework/maker-cli/utils/process.mjs +52 -0
  198. package/fastify/src/framework/maker-cli/utils/project-commands.mjs +201 -0
  199. package/fastify/src/framework/modules/discover.ts +57 -0
  200. package/fastify/src/framework/modules/routes.ts +31 -0
  201. package/fastify/src/framework/notification/channels/broadcast.ts +15 -0
  202. package/fastify/src/framework/notification/channels/mail.ts +42 -0
  203. package/fastify/src/framework/notification/contracts.ts +29 -0
  204. package/fastify/src/framework/notification/index.ts +59 -0
  205. package/fastify/src/framework/notification/registry.ts +15 -0
  206. package/fastify/src/framework/providers/adapter.ts +149 -0
  207. package/fastify/src/framework/providers/contracts.ts +126 -0
  208. package/fastify/src/framework/providers/loader.ts +105 -0
  209. package/fastify/src/framework/providers/registry.ts +88 -0
  210. package/fastify/src/framework/queue/clear.ts +22 -0
  211. package/fastify/src/framework/queue/queue.ts +401 -0
  212. package/fastify/src/framework/queue/ui.ts +194 -0
  213. package/fastify/src/framework/queue/worker.ts +61 -0
  214. package/fastify/src/framework/realtime/broadcast.ts +70 -0
  215. package/fastify/src/framework/realtime/index.ts +2 -0
  216. package/fastify/src/framework/realtime/socket-cookie.ts +72 -0
  217. package/fastify/src/framework/realtime/socket.ts +149 -0
  218. package/fastify/src/framework/realtime/types.ts +6 -0
  219. package/fastify/src/framework/realtime/ui.ts +16 -0
  220. package/fastify/src/framework/redis/client.ts +148 -0
  221. package/fastify/src/framework/runtime/runtime.ts +23 -0
  222. package/fastify/src/framework/runtime/server-adapter.ts +33 -0
  223. package/fastify/src/framework/scheduler/lock.ts +128 -0
  224. package/fastify/src/framework/scheduler/run.ts +48 -0
  225. package/fastify/src/framework/scheduler/scheduler.ts +155 -0
  226. package/fastify/src/framework/server.ts +162 -0
  227. package/fastify/src/framework/session/session.ts +160 -0
  228. package/fastify/src/framework/storage/storage.ts +744 -0
  229. package/fastify/src/framework/support/cookie.ts +71 -0
  230. package/fastify/src/framework/support/jwt.ts +33 -0
  231. package/fastify/src/framework/support/lifecycle.ts +35 -0
  232. package/fastify/src/framework/support/logger.ts +148 -0
  233. package/fastify/src/framework/support/mail.ts +56 -0
  234. package/fastify/src/framework/support/password.ts +23 -0
  235. package/fastify/src/framework/support/status-codes.ts +75 -0
  236. package/fastify/src/framework/support/upload.ts +181 -0
  237. package/fastify/src/framework/support/url.ts +41 -0
  238. package/fastify/src/middlewares/auth-middleware.ts +134 -0
  239. package/fastify/src/middlewares/role-middleware.ts +19 -0
  240. package/fastify/src/modules/auth/__tests__/forgetpass.test.ts +200 -0
  241. package/fastify/src/modules/auth/__tests__/login.test.ts +171 -0
  242. package/fastify/src/modules/auth/__tests__/logout.test.ts +60 -0
  243. package/fastify/src/modules/auth/__tests__/logoutalldevices.test.ts +49 -0
  244. package/fastify/src/modules/auth/__tests__/me.test.ts +91 -0
  245. package/fastify/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
  246. package/fastify/src/modules/auth/__tests__/register.test.ts +291 -0
  247. package/fastify/src/modules/auth/__tests__/resetpass.test.ts +206 -0
  248. package/fastify/src/modules/auth/__tests__/verifyemail.test.ts +183 -0
  249. package/fastify/src/modules/auth/controllers/auth.ts +252 -0
  250. package/fastify/src/modules/auth/controllers/role.ts +35 -0
  251. package/fastify/src/modules/auth/database/models/role.ts +14 -0
  252. package/fastify/src/modules/auth/database/models/user.ts +46 -0
  253. package/fastify/src/modules/auth/database/seeders/role.ts +18 -0
  254. package/fastify/src/modules/auth/database/seeders/user.ts +35 -0
  255. package/fastify/src/modules/auth/facade.ts +12 -0
  256. package/fastify/src/modules/auth/helpers/auth.ts +115 -0
  257. package/fastify/src/modules/auth/jobs/forgetpass.ts +20 -0
  258. package/fastify/src/modules/auth/jobs/registeruser.ts +23 -0
  259. package/fastify/src/modules/auth/jobs/verifyemail.ts +18 -0
  260. package/fastify/src/modules/auth/routes/index.ts +185 -0
  261. package/fastify/src/modules/auth/routes/role.ts +56 -0
  262. package/fastify/src/modules/auth/schemas/auth.ts +116 -0
  263. package/fastify/src/modules/auth/services/forgetpassword.ts +66 -0
  264. package/fastify/src/modules/auth/services/login.ts +63 -0
  265. package/fastify/src/modules/auth/services/logout.ts +25 -0
  266. package/fastify/src/modules/auth/services/logoutalldevices.ts +19 -0
  267. package/fastify/src/modules/auth/services/me.ts +31 -0
  268. package/fastify/src/modules/auth/services/refreshtoken.ts +110 -0
  269. package/fastify/src/modules/auth/services/register.ts +141 -0
  270. package/fastify/src/modules/auth/services/resetpassword.ts +69 -0
  271. package/fastify/src/modules/auth/services/verifyemail.ts +77 -0
  272. package/fastify/src/modules/auth/types/auth.ts +31 -0
  273. package/fastify/src/providers/README.md +107 -0
  274. package/fastify/src/providers/notification.ts +4 -0
  275. package/fastify/src/resources/index.html +17 -0
  276. package/fastify/src/resources/src/App.vue +5 -0
  277. package/fastify/src/resources/src/assets/css/styles.css +14761 -0
  278. package/fastify/src/resources/src/assets/css/styles.css.map +1 -0
  279. package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin-ext.woff2 +0 -0
  280. package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin.woff2 +0 -0
  281. package/fastify/src/resources/src/assets/images/favicon/favicon.ico +0 -0
  282. package/fastify/src/resources/src/assets/images/logo.png +0 -0
  283. package/fastify/src/resources/src/assets/scss/custom.css +217 -0
  284. package/fastify/src/resources/src/assets/scss/custom.css.map +1 -0
  285. package/fastify/src/resources/src/assets/scss/custom.scss +1346 -0
  286. package/fastify/src/resources/src/components/Button.vue +32 -0
  287. package/fastify/src/resources/src/components/Checkbox.vue +32 -0
  288. package/fastify/src/resources/src/components/Datepicker.vue +106 -0
  289. package/fastify/src/resources/src/components/FeatureButton.vue +53 -0
  290. package/fastify/src/resources/src/components/FloatButton.vue +33 -0
  291. package/fastify/src/resources/src/components/Href.vue +32 -0
  292. package/fastify/src/resources/src/components/Input.vue +243 -0
  293. package/fastify/src/resources/src/components/InputGroup.vue +147 -0
  294. package/fastify/src/resources/src/components/InputPasswordToggle.vue +229 -0
  295. package/fastify/src/resources/src/components/Modal.vue +71 -0
  296. package/fastify/src/resources/src/components/Pagebar.vue +26 -0
  297. package/fastify/src/resources/src/components/Refresh.vue +19 -0
  298. package/fastify/src/resources/src/components/Select.vue +384 -0
  299. package/fastify/src/resources/src/components/Spinner.vue +42 -0
  300. package/fastify/src/resources/src/components/Switch.vue +60 -0
  301. package/fastify/src/resources/src/components/TextArea.vue +117 -0
  302. package/fastify/src/resources/src/components/Toast.vue +56 -0
  303. package/fastify/src/resources/src/components/datatable/DataTableSkeleton.vue +98 -0
  304. package/fastify/src/resources/src/components/datatable/Pagination.vue +147 -0
  305. package/fastify/src/resources/src/components/datatable/SelectOpption.vue +64 -0
  306. package/fastify/src/resources/src/components/datatable/index.vue +199 -0
  307. package/fastify/src/resources/src/composables/useAuth.ts +52 -0
  308. package/fastify/src/resources/src/helpers/assets.ts +51 -0
  309. package/fastify/src/resources/src/helpers/nformatter.ts +10 -0
  310. package/fastify/src/resources/src/helpers/utils.ts +75 -0
  311. package/fastify/src/resources/src/layouts/AuthLayout.vue +20 -0
  312. package/fastify/src/resources/src/layouts/Layout/Footer.vue +23 -0
  313. package/fastify/src/resources/src/layouts/Layout/Header.vue +124 -0
  314. package/fastify/src/resources/src/layouts/Layout/Sidebar.vue +137 -0
  315. package/fastify/src/resources/src/layouts/Layout/index.vue +87 -0
  316. package/fastify/src/resources/src/main.ts +28 -0
  317. package/fastify/src/resources/src/pages/auth/forgetPassword.vue +76 -0
  318. package/fastify/src/resources/src/pages/auth/login.vue +96 -0
  319. package/fastify/src/resources/src/pages/auth/register.vue +129 -0
  320. package/fastify/src/resources/src/pages/auth/resetPassword.vue +110 -0
  321. package/fastify/src/resources/src/pages/auth/verifyEmail.vue +64 -0
  322. package/fastify/src/resources/src/pages/dashboard/index.vue +66 -0
  323. package/fastify/src/resources/src/plugins/axios.ts +31 -0
  324. package/fastify/src/resources/src/plugins/browserDetect.ts +55 -0
  325. package/fastify/src/resources/src/plugins/dialog.ts +216 -0
  326. package/fastify/src/resources/src/plugins/gum.ts +683 -0
  327. package/fastify/src/resources/src/plugins/pulse.ts +157 -0
  328. package/fastify/src/resources/src/plugins/routeProgress.ts +87 -0
  329. package/fastify/src/resources/src/router/index.ts +85 -0
  330. package/fastify/src/resources/src/stores/admin-ui.ts +148 -0
  331. package/fastify/src/resources/src/stores/auth.ts +187 -0
  332. package/fastify/src/resources/tsconfig.json +19 -0
  333. package/fastify/src/resources/vite.config.ts +64 -0
  334. package/fastify/src/storage/app/private/.gitkeep +0 -0
  335. package/fastify/src/storage/app/public/.gitkeep +0 -0
  336. package/fastify/src/storage/app/tmp/generated/.gitkeep +0 -0
  337. package/fastify/src/storage/database/nexwire.sqlite +0 -0
  338. package/fastify/src/storage/logs/.gitkeep +0 -0
  339. package/fastify/src/storage/logs/app.log +0 -0
  340. package/fastify/src/storage/logs/fatal.log +0 -0
  341. package/fastify/src/storage/tmp/.gitkeep +0 -0
  342. package/fastify/src/storage/trash/modules/.gitkeep +0 -0
  343. package/fastify/src/types/global.d.ts +32 -0
  344. package/fastify/tsconfig.json +19 -0
  345. package/fastify/vitest.config.ts +38 -0
  346. package/hono/.env +1 -1
  347. package/hono/package.json +1 -1
  348. package/hono/src/config/database.ts +48 -29
  349. package/hono/src/config/index.ts +83 -83
  350. package/hono/src/config/openapi.ts +6 -24
  351. package/hono/src/config/queue.ts +30 -30
  352. package/hono/src/config/validate.ts +50 -40
  353. package/hono/src/database/migrations/sqlite/0000_init.sql +47 -47
  354. package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  355. package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
  356. package/hono/src/framework/cache/cache.ts +40 -8
  357. package/hono/src/framework/circuit-breaker/cb.ts +88 -60
  358. package/hono/src/framework/database/connection.ts +52 -27
  359. package/hono/src/framework/events/dispatcher.ts +61 -49
  360. package/hono/src/framework/facade.ts +39 -1
  361. package/hono/src/framework/http/logger.ts +86 -81
  362. package/hono/src/framework/http/openapi.ts +7 -9
  363. package/hono/src/framework/maker-cli/runtime/core.mjs +1 -2
  364. package/hono/src/framework/notification/channels/broadcast.ts +15 -0
  365. package/hono/src/framework/notification/channels/mail.ts +42 -0
  366. package/hono/src/framework/notification/contracts.ts +29 -0
  367. package/hono/src/framework/notification/index.ts +19 -94
  368. package/hono/src/framework/notification/registry.ts +15 -0
  369. package/hono/src/framework/providers/adapter.ts +27 -17
  370. package/hono/src/framework/queue/queue.ts +43 -44
  371. package/hono/src/framework/realtime/broadcast.ts +29 -5
  372. package/hono/src/framework/realtime/socket.ts +217 -196
  373. package/hono/src/framework/redis/client.ts +148 -151
  374. package/hono/src/framework/scheduler/lock.ts +128 -116
  375. package/hono/src/framework/server.ts +27 -29
  376. package/hono/src/framework/session/session.ts +62 -14
  377. package/hono/src/framework/storage/storage.ts +744 -785
  378. package/hono/src/framework/support/mail.ts +56 -48
  379. package/hono/src/middlewares/auth-middleware.ts +1 -2
  380. package/hono/src/middlewares/role-middleware.ts +3 -13
  381. package/hono/src/modules/auth/routes/index.ts +1 -2
  382. package/hono/src/providers/notification.ts +4 -0
  383. package/hono/src/resources/src/components/Switch.vue +60 -65
  384. package/hono/src/resources/src/layouts/Layout/Header.vue +124 -105
  385. package/hono/src/resources/src/pages/auth/forgetPassword.vue +23 -29
  386. package/hono/src/resources/src/pages/auth/login.vue +26 -29
  387. package/hono/src/resources/src/pages/auth/register.vue +36 -40
  388. package/hono/src/resources/src/pages/auth/resetPassword.vue +29 -37
  389. package/hono/src/resources/src/pages/auth/verifyEmail.vue +15 -19
  390. package/hono/src/resources/src/plugins/gum.ts +519 -203
  391. package/hono/src/resources/src/plugins/pulse.ts +157 -147
  392. package/hono/src/resources/src/stores/auth.ts +9 -6
  393. package/hono/src/resources/vite.config.ts +54 -43
  394. package/hono/src/storage/database/nexwire.sqlite +0 -0
  395. package/hono/src/storage/logs/app.log +0 -0
  396. package/hono/src/storage/logs/fatal.log +0 -0
  397. package/hono/src/types/global.d.ts +32 -26
  398. package/package.json +4 -3
  399. package/src/index.js +143 -15
@@ -0,0 +1,25 @@
1
+ # Server infra env template (base values)
2
+ # Remote: copy to deploy/server/.env and use as-is
3
+ # Local: copy to deploy/server/.env then override in deploy/server/.env.local
4
+ LETSENCRYPT_EMAIL=ops@example.com
5
+ PROXY_HTTP_PORT=80
6
+ PROXY_HTTPS_PORT=443
7
+
8
+ MYSQL_ROOT_PASSWORD=change-me-root
9
+ MYSQL_DATABASE=nexwire
10
+ MYSQL_PORT=4000
11
+
12
+ POSTGRES_USER=postgres
13
+ POSTGRES_PASSWORD=change-me-postgres
14
+ POSTGRES_DB=postgres
15
+ POSTGRES_PORT=4001
16
+
17
+ REDIS=true
18
+
19
+ PHPMYADMIN_DOMAIN=pma.example.com
20
+ PGADMIN_DOMAIN=pga.example.com
21
+ PGADMIN_DEFAULT_EMAIL=admin@example.com
22
+ PGADMIN_DEFAULT_PASSWORD=admin123
23
+ PHPMYADMIN_LOCAL_PORT=127.0.0.1:8082
24
+ PGADMIN_LOCAL_PORT=127.0.0.1:8083
25
+
@@ -0,0 +1,16 @@
1
+ # nginx-vhost overrides
2
+
3
+ This folder holds per-domain nginx overrides for `jwilder/nginx-proxy`.
4
+
5
+ ## Usage
6
+
7
+ 1. Copy `app.example.com` to your real app domain file name.
8
+ 2. Edit rules as needed (timeouts, upload limits, auth for `/board`, etc).
9
+ 3. Recreate or reload proxy container if needed.
10
+
11
+ Example:
12
+
13
+ ```bash
14
+ cp deploy/server/nginx-vhost/app.example.com deploy/server/nginx-vhost/api.yourdomain.com
15
+ ```
16
+
@@ -0,0 +1,13 @@
1
+ # Custom nginx vhost overrides for app.example.com
2
+ # File name should match your APP_DOMAIN exactly.
3
+
4
+ client_max_body_size 50m;
5
+ proxy_read_timeout 300;
6
+ proxy_send_timeout 300;
7
+
8
+ # Protect Queue Dashboard in production
9
+ location /board {
10
+ auth_basic "Restricted";
11
+ auth_basic_user_file /etc/nginx/vhost.d/.htpasswd;
12
+ }
13
+
@@ -0,0 +1,14 @@
1
+ {
2
+ "Servers": {
3
+ "1": {
4
+ "Name": "postgres-global",
5
+ "Group": "Servers",
6
+ "Host": "postgres-global",
7
+ "Port": 5432,
8
+ "MaintenanceDB": "postgres",
9
+ "Username": "postgres",
10
+ "SSLMode": "prefer"
11
+ }
12
+ }
13
+ }
14
+
@@ -0,0 +1,7 @@
1
+ appendonly yes
2
+ maxmemory 512mb
3
+ maxmemory-policy noeviction
4
+ save 900 1
5
+ save 300 10
6
+ save 60 10000
7
+
@@ -0,0 +1,54 @@
1
+ [supervisord]
2
+ nodaemon=true
3
+ user=root
4
+ logfile=/dev/null
5
+ logfile_maxbytes=0
6
+ pidfile=/var/run/supervisord.pid
7
+ loglevel=info
8
+
9
+ [program:migrate]
10
+ command=/bin/sh -lc "/bin/sh /app/deploy/scripts/auto-migrate.sh {{RUNTIME_EXEC}}"
11
+ directory=/app
12
+ autostart=true
13
+ autorestart=false
14
+ priority=5
15
+ startsecs=0
16
+ startretries=1
17
+ exitcodes=0
18
+ stdout_logfile=/dev/stdout
19
+ stderr_logfile=/dev/stderr
20
+ stdout_logfile_maxbytes=0
21
+ stderr_logfile_maxbytes=0
22
+
23
+ [program:api]
24
+ command=/bin/sh -lc "{{RUNTIME_EXEC}} src/framework/maker-cli/index.mjs serve --prod --runtime={{RUNTIME_NAME}}"
25
+ directory=/app
26
+ autostart=true
27
+ autorestart=true
28
+ priority=20
29
+ startsecs=3
30
+ startretries=5
31
+ stopsignal=TERM
32
+ stopwaitsecs=20
33
+ redirect_stderr=true
34
+ stdout_logfile=/dev/stdout
35
+ stdout_logfile_maxbytes=0
36
+ stopasgroup=true
37
+ killasgroup=true
38
+
39
+ [program:scheduler]
40
+ command=/bin/sh -lc "{{RUNTIME_EXEC}} src/framework/maker-cli/index.mjs schedule:work --prod --runtime={{RUNTIME_NAME}}"
41
+ directory=/app
42
+ autostart=true
43
+ autorestart=true
44
+ priority=40
45
+ startsecs=3
46
+ startretries=5
47
+ stopsignal=TERM
48
+ stopwaitsecs=20
49
+ redirect_stderr=true
50
+ stdout_logfile=/dev/stdout
51
+ stdout_logfile_maxbytes=0
52
+ stopasgroup=true
53
+ killasgroup=true
54
+
@@ -0,0 +1,70 @@
1
+ [supervisord]
2
+ nodaemon=true
3
+ user=root
4
+ logfile=/dev/null
5
+ logfile_maxbytes=0
6
+ pidfile=/var/run/supervisord.pid
7
+ loglevel=info
8
+
9
+ [program:migrate]
10
+ command=/bin/sh -lc "/bin/sh /app/deploy/scripts/auto-migrate.sh {{RUNTIME_EXEC}}"
11
+ directory=/app
12
+ autostart=true
13
+ autorestart=false
14
+ priority=5
15
+ startsecs=0
16
+ startretries=1
17
+ exitcodes=0
18
+ stdout_logfile=/dev/stdout
19
+ stderr_logfile=/dev/stderr
20
+ stdout_logfile_maxbytes=0
21
+ stderr_logfile_maxbytes=0
22
+
23
+ [program:api]
24
+ command=/bin/sh -lc "{{RUNTIME_EXEC}} src/framework/maker-cli/index.mjs serve --prod --runtime={{RUNTIME_NAME}}"
25
+ directory=/app
26
+ autostart=true
27
+ autorestart=true
28
+ priority=20
29
+ startsecs=3
30
+ startretries=5
31
+ stopsignal=TERM
32
+ stopwaitsecs=20
33
+ redirect_stderr=true
34
+ stdout_logfile=/dev/stdout
35
+ stdout_logfile_maxbytes=0
36
+ stopasgroup=true
37
+ killasgroup=true
38
+
39
+ [program:queue]
40
+ command=/bin/sh -lc "{{RUNTIME_EXEC}} src/framework/maker-cli/index.mjs queue:work --queue=default,mail,maintenance --prod --runtime={{RUNTIME_NAME}}"
41
+ directory=/app
42
+ autostart=true
43
+ autorestart=true
44
+ priority=30
45
+ startsecs=3
46
+ startretries=5
47
+ stopsignal=TERM
48
+ stopwaitsecs=20
49
+ redirect_stderr=true
50
+ stdout_logfile=/dev/stdout
51
+ stdout_logfile_maxbytes=0
52
+ stopasgroup=true
53
+ killasgroup=true
54
+
55
+ [program:scheduler]
56
+ command=/bin/sh -lc "{{RUNTIME_EXEC}} src/framework/maker-cli/index.mjs schedule:work --prod --runtime={{RUNTIME_NAME}}"
57
+ directory=/app
58
+ autostart=true
59
+ autorestart=true
60
+ priority=40
61
+ startsecs=3
62
+ startretries=5
63
+ stopsignal=TERM
64
+ stopwaitsecs=20
65
+ redirect_stderr=true
66
+ stdout_logfile=/dev/stdout
67
+ stdout_logfile_maxbytes=0
68
+ stopasgroup=true
69
+ killasgroup=true
70
+
@@ -0,0 +1,25 @@
1
+ {
2
+ "steps": [
3
+ {
4
+ "name": "Generate deploy files",
5
+ "run": "deploy:init --force",
6
+ "enabled": false
7
+ },
8
+ {
9
+ "name": "Start shared infra",
10
+ "run": "deploy:workflow --server-only",
11
+ "enabled": true
12
+ },
13
+ {
14
+ "name": "Import database dump (optional)",
15
+ "run": "deploy:db:import --file=deploy/nexwire.sql --database=nexwire",
16
+ "enabled": false
17
+ },
18
+ {
19
+ "name": "Start app stack",
20
+ "run": "deploy:workflow --app-only",
21
+ "enabled": true
22
+ }
23
+ ]
24
+ }
25
+
@@ -0,0 +1,24 @@
1
+ {
2
+ "remote": {
3
+ "host": "127.0.0.1",
4
+ "user": "deploy",
5
+ "port": 22,
6
+ "keyPath": "~/.ssh/id_rsa",
7
+ "targetPath": "/home/deploy/nexwire"
8
+ },
9
+ "upload": {
10
+ "source": ".",
11
+ "targetSubPath": "."
12
+ },
13
+ "databaseImport": {
14
+ "enabled": false,
15
+ "file": "deploy/nexwire.sql",
16
+ "database": "nexwire",
17
+ "container": "mysql-global",
18
+ "user": "root"
19
+ },
20
+ "preDeployCommands": [
21
+ "docker rm -f old-app 2>/dev/null || true"
22
+ ]
23
+ }
24
+
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Public API for the {{module}} module.
3
+ *
4
+ * Other modules import from this file, never from controllers/ or services/ directly.
5
+ * Re-export the pieces they are allowed to depend on:
6
+ *
7
+ * export { list, findOne } from "./services/{{controller}}.js";
8
+ *
9
+ * Rule: a facade must never import another module's facade.
10
+ */
11
+
12
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Why: Reusable {{name}} logic that does not belong to a controller or service.
3
+ * When: Called by controllers, services, jobs, or other helpers.
4
+ * Where: src/modules/{{module}}/helpers/{{name}}.ts
5
+ */
6
+
7
+ /**
8
+ * Why: Placeholder shared helper.
9
+ * When: Replace this with real logic and rename it.
10
+ * Where: Anywhere inside the {{module}} module.
11
+ */
12
+ export function {{Name}}Helper<T>(value: T): T {
13
+ return value;
14
+ }
@@ -0,0 +1,25 @@
1
+ import { DateTime } from "luxon";
2
+ import { dispatchEvent, shouldQueue } from "@/framework/facade.js";
3
+
4
+ /**
5
+ * event: Event name this worker listens to.
6
+ * queue: Queue name where jobs are consumed from.
7
+ * handler: Async processor that receives `job.data`.
8
+ */
9
+ shouldQueue("{{module}}.{{name}}", "default", async job => {
10
+ console.log("{{module}}.{{name}}", job.data);
11
+
12
+ /**
13
+ * dispatchEvent name: Event id to publish to listeners.
14
+ * payload: Data sent to queue/broadcast consumers.
15
+ * queue: Optional queue target for async event consumers.
16
+ * broadcast: Optional realtime fanout (all/users/roles/rooms).
17
+ */
18
+ // await dispatchEvent("{{module}}.{{name}}.finished", {
19
+ // ...job.data,
20
+ // processedAt: DateTime.now(),
21
+ // });
22
+
23
+ return { ok: true, processedAt: DateTime.now().toISO() };
24
+ });
25
+
@@ -0,0 +1,15 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+
3
+ /**
4
+ * Why: Route-scoped middleware for the {{module}} module.
5
+ * When: Attach to a route or route scope inside routes/*.ts.
6
+ * Where: src/modules/{{module}}/middlewares/{{name}}.ts
7
+ *
8
+ * Register it in a route file:
9
+ * import { {{camel}}Middleware } from "@/modules/{{module}}/middlewares/{{name}}.js";
10
+ * scope.addHook("preHandler", {{camel}}Middleware);
11
+ * // or: app.route({ ..., preHandler: {{camel}}Middleware, handler })
12
+ */
13
+ export async function {{camel}}Middleware(_request: FastifyRequest, _reply: FastifyReply) {
14
+ // Guard the route here; send a reply to stop, or return to continue the chain.
15
+ }
@@ -0,0 +1,15 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+ import { db } from "@/framework/facade.js";
3
+
4
+ /**
5
+ * Why: Runs before route handlers to enforce cross-cutting rules for {{name}}.
6
+ * When: Attach to a route scope or a specific route.
7
+ * Where: Add it globally: app.addHook("preHandler", {{camel}}Middleware), or per route:
8
+ * app.route({ ..., preHandler: {{camel}}Middleware, handler })
9
+ * How: request.auth is populated when authMiddleware ran first; db gives
10
+ * typed access to your module models. Return nothing to continue, or
11
+ * return a reply to stop the request.
12
+ */
13
+ export async function {{camel}}Middleware(_request: FastifyRequest, _reply: FastifyReply) {
14
+ // Guard the request here; send a reply to stop, or return to continue.
15
+ }
@@ -0,0 +1,10 @@
1
+ import { relations } from "drizzle-orm";
2
+ import { int, mysqlTable, timestamp, varchar } from "drizzle-orm/mysql-core";
3
+
4
+ export const {{tableVariable}} = mysqlTable("{{tableName}}", {
5
+ id: int("id").autoincrement().primaryKey(),
6
+ name: varchar("name", { length: 255 }).notNull(),
7
+ createdAt: timestamp("created_at").defaultNow().notNull(),
8
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
9
+ });
10
+
@@ -0,0 +1,10 @@
1
+ import { relations } from "drizzle-orm";
2
+ import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
3
+
4
+ export const {{tableVariable}} = pgTable("{{tableName}}", {
5
+ id: serial("id").primaryKey(),
6
+ name: text("name").notNull(),
7
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
8
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
9
+ });
10
+
@@ -0,0 +1,10 @@
1
+ import { relations } from "drizzle-orm";
2
+ import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
3
+
4
+ export const {{tableVariable}} = sqliteTable("{{tableName}}", {
5
+ id: integer("id").primaryKey({ autoIncrement: true }),
6
+ name: text("name").notNull(),
7
+ createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => new Date()).notNull(),
8
+ updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn(() => new Date()).notNull()
9
+ });
10
+
@@ -0,0 +1,79 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+ import { HttpStatusCodes, resolveNotificationProvider } from "@/framework/facade.js";
3
+ import type { NotifiableTarget } from "@/framework/providers/contracts.js";
4
+
5
+ type IdRequest = FastifyRequest<{ Params: { id: string } }>;
6
+ type ListRequest = FastifyRequest<{ Querystring: { page?: string; perPage?: string } }>;
7
+
8
+ /** Read the authenticated owner. A missing auth context means the request is anonymous. */
9
+ function targetFrom(request: FastifyRequest): NotifiableTarget | null {
10
+ const auth = request.auth;
11
+ if (!auth || auth.id === undefined || auth.id === null) return null;
12
+ return { type: "{{notifiable}}", id: auth.id };
13
+ }
14
+
15
+ function unavailable(reply: FastifyReply) {
16
+ return reply.code(HttpStatusCodes.SERVICE_UNAVAILABLE).send({
17
+ message: "Notification provider not available. Restore src/modules/notification or register one in src/providers/."
18
+ });
19
+ }
20
+
21
+ function unauthorized(reply: FastifyReply) {
22
+ return reply.code(HttpStatusCodes.UNAUTHORIZED).send({ message: "Unauthorized" });
23
+ }
24
+
25
+ export const list = async (request: ListRequest, reply: FastifyReply) => {
26
+ const provider = await resolveNotificationProvider();
27
+ if (!provider) return unavailable(reply);
28
+
29
+ const target = targetFrom(request);
30
+ if (!target) return unauthorized(reply);
31
+
32
+ const page = Math.max(1, Number(request.query.page) || 1);
33
+ const perPage = Math.min(50, Math.max(1, Number(request.query.perPage) || 20));
34
+
35
+ return reply.code(HttpStatusCodes.OK).send(await provider.list(target, { page, perPage }));
36
+ };
37
+
38
+ export const unreadCount = async (request: FastifyRequest, reply: FastifyReply) => {
39
+ const provider = await resolveNotificationProvider();
40
+ if (!provider) return unavailable(reply);
41
+
42
+ const target = targetFrom(request);
43
+ if (!target) return unauthorized(reply);
44
+
45
+ return reply.code(HttpStatusCodes.OK).send({ count: await provider.unreadCount(target) });
46
+ };
47
+
48
+ export const markRead = async (request: IdRequest, reply: FastifyReply) => {
49
+ const provider = await resolveNotificationProvider();
50
+ if (!provider) return unavailable(reply);
51
+
52
+ const target = targetFrom(request);
53
+ if (!target) return unauthorized(reply);
54
+
55
+ await provider.markRead(target, String(request.params.id));
56
+ return reply.code(HttpStatusCodes.OK).send({ ok: true });
57
+ };
58
+
59
+ export const markAllRead = async (request: FastifyRequest, reply: FastifyReply) => {
60
+ const provider = await resolveNotificationProvider();
61
+ if (!provider) return unavailable(reply);
62
+
63
+ const target = targetFrom(request);
64
+ if (!target) return unauthorized(reply);
65
+
66
+ await provider.markAllRead(target);
67
+ return reply.code(HttpStatusCodes.OK).send({ ok: true });
68
+ };
69
+
70
+ export const remove = async (request: IdRequest, reply: FastifyReply) => {
71
+ const provider = await resolveNotificationProvider();
72
+ if (!provider) return unavailable(reply);
73
+
74
+ const target = targetFrom(request);
75
+ if (!target) return unauthorized(reply);
76
+
77
+ await provider.remove(target, String(request.params.id));
78
+ return reply.code(HttpStatusCodes.OK).send({ ok: true });
79
+ };
@@ -0,0 +1,11 @@
1
+ import { mail, shouldQueue } from "@/framework/facade.js";
2
+ import { logger } from "@/framework/support/logger.js";
3
+
4
+ shouldQueue("notification:mail", "mail", async (job) => {
5
+ const { to, subject, html } = job.data;
6
+
7
+ await mail.sendMail({ to, subject, html });
8
+ logger.info("Notification mail sent", { to, subject });
9
+
10
+ return { ok: true };
11
+ });
@@ -0,0 +1,29 @@
1
+ import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
2
+
3
+ /**
4
+ * Why: Notifications are stored by a standalone module, not by auth.
5
+ * When: `notify()` writes a row and `/api/notification/*` reads it back.
6
+ * Where: `src/modules/notification/database/models/notification.ts`.
7
+ * How: A polymorphic owner (`notifiable_type` + `notifiable_id`) with no
8
+ * foreign key, so deleting or replacing auth cannot cascade-delete
9
+ * notifications and a UUID or numeric owner id stores the same way.
10
+ */
11
+ export const notifications = sqliteTable(
12
+ "notifications",
13
+ {
14
+ id: text("id").primaryKey(),
15
+ notifiableType: text("notifiable_type").notNull().default("user"),
16
+ notifiableId: text("notifiable_id").notNull(),
17
+ type: text("type").notNull(),
18
+ title: text("title").notNull(),
19
+ body: text("body"),
20
+ data: text("data"),
21
+ link: text("link"),
22
+ readAt: integer("read_at", { mode: "timestamp" }),
23
+ createdAt: integer("created_at", { mode: "timestamp" }).defaultNow().notNull()
24
+ },
25
+ (table) => [
26
+ index("notifications_notifiable_read_idx").on(table.notifiableType, table.notifiableId, table.readAt),
27
+ index("notifications_notifiable_created_idx").on(table.notifiableType, table.notifiableId, table.createdAt)
28
+ ]
29
+ );
@@ -0,0 +1,174 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { and, count, desc, eq, isNull } from "drizzle-orm";
3
+ import { db } from "@/framework/database/connection.js";
4
+ import type {
5
+ NotifiableTarget,
6
+ NotificationCreateInput,
7
+ NotificationListOptions,
8
+ NotificationPage,
9
+ NotificationProvider,
10
+ NotificationRecord
11
+ } from "@/framework/providers/contracts.js";
12
+ import { registerNotificationProvider } from "@/framework/providers/registry.js";
13
+ import { notifications } from "@/modules/{{module}}/database/models/notification.js";
14
+
15
+ /** Normalize a target into the polymorphic pair stored in the table. */
16
+ function normalizeTarget(target: NotifiableTarget) {
17
+ const type = String(target?.type || "user").trim() || "user";
18
+ if (target?.id === undefined || target?.id === null || target?.id === "") {
19
+ throw new Error("A notification target needs an id.");
20
+ }
21
+ return { type, id: String(target.id) };
22
+ }
23
+
24
+ /** Build the owner scope shared by every read and write. */
25
+ function scope(target: NotifiableTarget) {
26
+ const normalized = normalizeTarget(target);
27
+ return {
28
+ target: normalized,
29
+ where: and(eq(notifications.notifiableType, normalized.type), eq(notifications.notifiableId, normalized.id))
30
+ };
31
+ }
32
+
33
+ function toIso(value: Date | string | number | null | undefined): string | null {
34
+ if (value === null || value === undefined) return null;
35
+ const date = value instanceof Date ? value : new Date(value);
36
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
37
+ }
38
+
39
+ function parseData(value: unknown): Record<string, unknown> | null {
40
+ if (!value) return null;
41
+ if (typeof value === "object") return value as Record<string, unknown>;
42
+ try {
43
+ const parsed = JSON.parse(String(value));
44
+ return parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : null;
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ /** Convert a DB row into the stable shape every consumer receives. */
51
+ function normalizeRow(row: typeof notifications.$inferSelect): NotificationRecord {
52
+ return {
53
+ id: String(row.id),
54
+ notifiableType: row.notifiableType,
55
+ notifiableId: row.notifiableId,
56
+ type: row.type,
57
+ title: row.title,
58
+ body: row.body ?? null,
59
+ data: parseData(row.data),
60
+ link: row.link ?? null,
61
+ readAt: toIso(row.readAt),
62
+ createdAt: toIso(row.createdAt) ?? new Date(0).toISOString()
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Why: Ships a working notification store so `notify()` needs no setup.
68
+ * When: Imported at boot by the provider loader (and lazily by the resolver).
69
+ * Where: `src/modules/notification/providers/notification.ts`.
70
+ * How: Drizzle-backed implementation of `NotificationProvider`. The primary
71
+ * key is a UUID string, so ids never depend on the auth table; numeric
72
+ * targets are accepted and stored as text by the contract.
73
+ */
74
+ export const notificationProvider: NotificationProvider = {
75
+ async create(target: NotifiableTarget, input: NotificationCreateInput): Promise<NotificationRecord> {
76
+ const { target: normalized } = scope(target);
77
+ const id = randomUUID();
78
+
79
+ await db.insert(notifications).values({
80
+ id,
81
+ notifiableType: normalized.type,
82
+ notifiableId: normalized.id,
83
+ type: input.type || "info",
84
+ title: input.title,
85
+ body: input.body ?? null,
86
+ link: input.link ?? null,
87
+ data: input.data ? JSON.stringify(input.data) : null
88
+ });
89
+
90
+ const [row] = await db.select().from(notifications).where(eq(notifications.id, id));
91
+ if (!row) throw new Error("Failed to create notification");
92
+ return normalizeRow(row);
93
+ },
94
+
95
+ async list(target: NotifiableTarget, options: NotificationListOptions = {}): Promise<NotificationPage> {
96
+ const { where } = scope(target);
97
+ const page = Math.max(1, Math.floor(Number(options.page) || 1));
98
+ const perPage = Math.min(100, Math.max(1, Math.floor(Number(options.perPage) || 20)));
99
+
100
+ const [totalRows, unreadRows, rows] = await Promise.all([
101
+ db.select({ count: count() }).from(notifications).where(where),
102
+ db.select({ count: count() }).from(notifications).where(and(where, isNull(notifications.readAt))),
103
+ db
104
+ .select()
105
+ .from(notifications)
106
+ .where(where)
107
+ .orderBy(desc(notifications.createdAt))
108
+ .limit(perPage)
109
+ .offset((page - 1) * perPage)
110
+ ]);
111
+
112
+ return {
113
+ data: rows.map(normalizeRow),
114
+ total: Number(totalRows[0]?.count ?? 0),
115
+ unread: Number(unreadRows[0]?.count ?? 0),
116
+ page,
117
+ perPage
118
+ };
119
+ },
120
+
121
+ async unreadCount(target: NotifiableTarget): Promise<number> {
122
+ const { where } = scope(target);
123
+ const rows = await db
124
+ .select({ count: count() })
125
+ .from(notifications)
126
+ .where(and(where, isNull(notifications.readAt)));
127
+ return Number(rows[0]?.count ?? 0);
128
+ },
129
+
130
+ async markRead(target: NotifiableTarget, id: number | string): Promise<boolean> {
131
+ const { where } = scope(target);
132
+ const notificationWhere = and(where, eq(notifications.id, String(id)));
133
+ const [existing] = await db.select({ id: notifications.id }).from(notifications).where(notificationWhere);
134
+ if (!existing) return false;
135
+
136
+ await db.update(notifications).set({ readAt: new Date() }).where(notificationWhere);
137
+ return true;
138
+ },
139
+
140
+ async markAllRead(target: NotifiableTarget): Promise<number> {
141
+ const { where } = scope(target);
142
+ const unreadWhere = and(where, isNull(notifications.readAt));
143
+ const rows = await db.select({ count: count() }).from(notifications).where(unreadWhere);
144
+ const unread = Number(rows[0]?.count ?? 0);
145
+ if (unread === 0) return 0;
146
+
147
+ await db.update(notifications).set({ readAt: new Date() }).where(unreadWhere);
148
+ return unread;
149
+ },
150
+
151
+ async remove(target: NotifiableTarget, id: number | string): Promise<boolean> {
152
+ const { where } = scope(target);
153
+ const notificationWhere = and(where, eq(notifications.id, String(id)));
154
+ const [existing] = await db.select({ id: notifications.id }).from(notifications).where(notificationWhere);
155
+ if (!existing) return false;
156
+
157
+ await db.delete(notifications).where(notificationWhere);
158
+ return true;
159
+ },
160
+
161
+ async deleteForTarget(target: NotifiableTarget): Promise<number> {
162
+ const { where } = scope(target);
163
+ const rows = await db.select({ count: count() }).from(notifications).where(where);
164
+ const total = Number(rows[0]?.count ?? 0);
165
+ if (total === 0) return 0;
166
+
167
+ await db.delete(notifications).where(where);
168
+ return total;
169
+ }
170
+ };
171
+
172
+ registerNotificationProvider(notificationProvider);
173
+
174
+ export default notificationProvider;