create-nexgen 1.0.4

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 (240) hide show
  1. package/package.json +26 -0
  2. package/src/index.js +108 -0
  3. package/template/.dockerignore +14 -0
  4. package/template/.env +58 -0
  5. package/template/.env.example +59 -0
  6. package/template/.prettierignore +5 -0
  7. package/template/.prettierrc +8 -0
  8. package/template/README.md +447 -0
  9. package/template/drizzle.config.ts +29 -0
  10. package/template/eslint.config.js +52 -0
  11. package/template/gitignore-stub +24 -0
  12. package/template/package.json +96 -0
  13. package/template/public/assets/AuthLayout-CbswhpjJ.js +1 -0
  14. package/template/public/assets/Button-_7aQ7gHL.js +1 -0
  15. package/template/public/assets/Input-CLNJXmKc.css +1 -0
  16. package/template/public/assets/Input-z8GI8Aqo.js +1 -0
  17. package/template/public/assets/InputPasswordToggle-BxlzVGp3.js +1 -0
  18. package/template/public/assets/InputPasswordToggle-C77FI9Eg.css +1 -0
  19. package/template/public/assets/Layout-DotR1sQC.js +1 -0
  20. package/template/public/assets/Refresh-BdqsPPBC.js +1 -0
  21. package/template/public/assets/admin-ui-CU34rLdN.js +1 -0
  22. package/template/public/assets/bootstrap-icons-BeopsB42.woff +0 -0
  23. package/template/public/assets/bootstrap-icons-mSm7cUeB.woff2 +0 -0
  24. package/template/public/assets/dashboard-CwybEyLc.js +1 -0
  25. package/template/public/assets/dashboard-Dc4d-Pi7.css +1 -0
  26. package/template/public/assets/forgetPassword-CKEJaXsq.js +1 -0
  27. package/template/public/assets/index-Bleyx5dm.js +64 -0
  28. package/template/public/assets/index-DUw8E6Yg.css +1 -0
  29. package/template/public/assets/login-DC7PTlQF.js +1 -0
  30. package/template/public/assets/realtime-test-BPQdrFym.css +1 -0
  31. package/template/public/assets/realtime-test-tQZ0rBEJ.js +1 -0
  32. package/template/public/assets/register-3O7Qs28C.js +1 -0
  33. package/template/public/assets/resetPassword-A5AzMWKs.js +1 -0
  34. package/template/public/assets/verifyEmail-DDBEQHOv.js +1 -0
  35. package/template/public/index.html +17 -0
  36. package/template/src/database/migrations/mysql/0000_init.sql +73 -0
  37. package/template/src/database/migrations/mysql/meta/0000_snapshot.json +484 -0
  38. package/template/src/database/migrations/mysql/meta/_journal.json +13 -0
  39. package/template/src/database/schema.ts +4 -0
  40. package/template/src/env.ts +107 -0
  41. package/template/src/framework/cache/cache.ts +81 -0
  42. package/template/src/framework/database/connection.ts +168 -0
  43. package/template/src/framework/database/optional-db-drivers.d.ts +9 -0
  44. package/template/src/framework/database/paginate.ts +200 -0
  45. package/template/src/framework/database/schema.ts +26 -0
  46. package/template/src/framework/database/seed.ts +33 -0
  47. package/template/src/framework/events/dispatcher.ts +57 -0
  48. package/template/src/framework/facade.ts +27 -0
  49. package/template/src/framework/http/app.ts +61 -0
  50. package/template/src/framework/http/cors.ts +19 -0
  51. package/template/src/framework/http/logger.ts +85 -0
  52. package/template/src/framework/http/openapi.ts +34 -0
  53. package/template/src/framework/http/ratelimiter.ts +13 -0
  54. package/template/src/framework/http/router.ts +76 -0
  55. package/template/src/framework/http/static.ts +33 -0
  56. package/template/src/framework/http/validation.ts +24 -0
  57. package/template/src/framework/kernel.ts +40 -0
  58. package/template/src/framework/maker-cli/src/index.mjs +51 -0
  59. package/template/src/framework/maker-cli/src/levels/level-1/env-db.mjs +57 -0
  60. package/template/src/framework/maker-cli/src/levels/level-1/file-ops.mjs +30 -0
  61. package/template/src/framework/maker-cli/src/levels/level-1/flags.mjs +16 -0
  62. package/template/src/framework/maker-cli/src/levels/level-1/help.mjs +24 -0
  63. package/template/src/framework/maker-cli/src/levels/level-1/naming.mjs +13 -0
  64. package/template/src/framework/maker-cli/src/levels/level-1/process.mjs +47 -0
  65. package/template/src/framework/maker-cli/src/levels/level-2/db/core.mjs +299 -0
  66. package/template/src/framework/maker-cli/src/levels/level-2/db/index.mjs +177 -0
  67. package/template/src/framework/maker-cli/src/levels/level-2/deploy/core.mjs +635 -0
  68. package/template/src/framework/maker-cli/src/levels/level-2/deploy/index.mjs +145 -0
  69. package/template/src/framework/maker-cli/src/levels/level-2/module/core.mjs +707 -0
  70. package/template/src/framework/maker-cli/src/levels/level-2/module/index.mjs +116 -0
  71. package/template/src/framework/maker-cli/src/levels/level-2/runtime/build-frontend.mjs +16 -0
  72. package/template/src/framework/maker-cli/src/levels/level-2/runtime/core.mjs +311 -0
  73. package/template/src/framework/maker-cli/src/levels/level-2/runtime/index.mjs +71 -0
  74. package/template/src/framework/maker-cli/stubs/controller/openapi.ts.stub +55 -0
  75. package/template/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +56 -0
  76. package/template/src/framework/maker-cli/stubs/controller/plain.ts.stub +57 -0
  77. package/template/src/framework/maker-cli/stubs/controller/schema.plain.ts.stub +13 -0
  78. package/template/src/framework/maker-cli/stubs/controller/schema.ts.stub +32 -0
  79. package/template/src/framework/maker-cli/stubs/deploy/Dockerfile.bun.stub +49 -0
  80. package/template/src/framework/maker-cli/stubs/deploy/Dockerfile.pnpm.stub +53 -0
  81. package/template/src/framework/maker-cli/stubs/deploy/Dockerfile.stub +49 -0
  82. package/template/src/framework/maker-cli/stubs/deploy/Dockerfile.yarn.stub +53 -0
  83. package/template/src/framework/maker-cli/stubs/deploy/README.stub +55 -0
  84. package/template/src/framework/maker-cli/stubs/deploy/compose/mysql.server.stub +29 -0
  85. package/template/src/framework/maker-cli/stubs/deploy/compose/postgres.server.stub +29 -0
  86. package/template/src/framework/maker-cli/stubs/deploy/compose/sqlite.stub +29 -0
  87. package/template/src/framework/maker-cli/stubs/deploy/env/mysql.server.stub +73 -0
  88. package/template/src/framework/maker-cli/stubs/deploy/env/postgres.server.stub +73 -0
  89. package/template/src/framework/maker-cli/stubs/deploy/env/sqlite.stub +72 -0
  90. package/template/src/framework/maker-cli/stubs/deploy/scripts/auto-migrate.sh.stub +15 -0
  91. package/template/src/framework/maker-cli/stubs/deploy/server/README.stub +77 -0
  92. package/template/src/framework/maker-cli/stubs/deploy/server/compose/noredis.stub +118 -0
  93. package/template/src/framework/maker-cli/stubs/deploy/server/compose/redis.dev.stub +131 -0
  94. package/template/src/framework/maker-cli/stubs/deploy/server/compose/redis.stub +129 -0
  95. package/template/src/framework/maker-cli/stubs/deploy/server/env/local.example.stub +10 -0
  96. package/template/src/framework/maker-cli/stubs/deploy/server/env/noredis.stub +24 -0
  97. package/template/src/framework/maker-cli/stubs/deploy/server/env/redis.stub +24 -0
  98. package/template/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/README.stub +15 -0
  99. package/template/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/app.example.com.stub +12 -0
  100. package/template/src/framework/maker-cli/stubs/deploy/server/pgadmin/servers.stub +13 -0
  101. package/template/src/framework/maker-cli/stubs/deploy/server/redis/redis.conf.stub +6 -0
  102. package/template/src/framework/maker-cli/stubs/deploy/supervisor/noredis.stub +53 -0
  103. package/template/src/framework/maker-cli/stubs/deploy/supervisor/redis.stub +69 -0
  104. package/template/src/framework/maker-cli/stubs/deploy/workflow/local.json.stub +24 -0
  105. package/template/src/framework/maker-cli/stubs/deploy/workflow/remote.json.stub +20 -0
  106. package/template/src/framework/maker-cli/stubs/example/console.ts.stub +33 -0
  107. package/template/src/framework/maker-cli/stubs/example/controller.ts.stub +503 -0
  108. package/template/src/framework/maker-cli/stubs/example/job.ts.stub +74 -0
  109. package/template/src/framework/maker-cli/stubs/example/route.api.ts.stub +206 -0
  110. package/template/src/framework/maker-cli/stubs/example/schema.ts.stub +41 -0
  111. package/template/src/framework/maker-cli/stubs/job/name.ts.stub +24 -0
  112. package/template/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +8 -0
  113. package/template/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +8 -0
  114. package/template/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +8 -0
  115. package/template/src/framework/maker-cli/stubs/notification/NotificationBell.vue.stub +218 -0
  116. package/template/src/framework/maker-cli/stubs/notification/controller.ts.stub +85 -0
  117. package/template/src/framework/maker-cli/stubs/notification/index.vue.stub +211 -0
  118. package/template/src/framework/maker-cli/stubs/notification/job.ts.stub +12 -0
  119. package/template/src/framework/maker-cli/stubs/notification/route.api.ts.stub +49 -0
  120. package/template/src/framework/maker-cli/stubs/notification/schema.ts.stub +25 -0
  121. package/template/src/framework/maker-cli/stubs/route/api.ts.stub +79 -0
  122. package/template/src/framework/maker-cli/stubs/route/plain.ts.stub +10 -0
  123. package/template/src/framework/maker-cli/stubs/schedule/name.ts.stub +35 -0
  124. package/template/src/framework/maker-cli/stubs/seeder/name.ts.stub +17 -0
  125. package/template/src/framework/modules/discover.ts +54 -0
  126. package/template/src/framework/modules/routes.ts +26 -0
  127. package/template/src/framework/notification/index.ts +109 -0
  128. package/template/src/framework/queue/clear.ts +20 -0
  129. package/template/src/framework/queue/queue.ts +213 -0
  130. package/template/src/framework/queue/ui.ts +104 -0
  131. package/template/src/framework/queue/worker.ts +33 -0
  132. package/template/src/framework/realtime/broadcast.ts +27 -0
  133. package/template/src/framework/realtime/index.ts +1 -0
  134. package/template/src/framework/realtime/socket-cookie.ts +65 -0
  135. package/template/src/framework/realtime/socket.ts +132 -0
  136. package/template/src/framework/realtime/types.ts +6 -0
  137. package/template/src/framework/realtime/ui.ts +16 -0
  138. package/template/src/framework/redis/client.ts +126 -0
  139. package/template/src/framework/scheduler/lock.ts +124 -0
  140. package/template/src/framework/scheduler/run.ts +26 -0
  141. package/template/src/framework/scheduler/scheduler.ts +82 -0
  142. package/template/src/framework/server.ts +147 -0
  143. package/template/src/framework/session/session.ts +116 -0
  144. package/template/src/framework/storage/storage.ts +743 -0
  145. package/template/src/framework/support/cookie.ts +78 -0
  146. package/template/src/framework/support/jwt.ts +45 -0
  147. package/template/src/framework/support/lifecycle.ts +35 -0
  148. package/template/src/framework/support/logger.ts +102 -0
  149. package/template/src/framework/support/mail.ts +43 -0
  150. package/template/src/framework/support/password.ts +23 -0
  151. package/template/src/framework/support/url.ts +25 -0
  152. package/template/src/middlewares/auth-middleware.ts +98 -0
  153. package/template/src/middlewares/role-middleware.ts +24 -0
  154. package/template/src/modules/auth/controllers/auth.controller.ts +445 -0
  155. package/template/src/modules/auth/controllers/auth.helpers.ts +110 -0
  156. package/template/src/modules/auth/controllers/auth.schema.ts +102 -0
  157. package/template/src/modules/auth/controllers/role.controller.ts +25 -0
  158. package/template/src/modules/auth/database/models/notifications.ts +22 -0
  159. package/template/src/modules/auth/database/models/role.ts +14 -0
  160. package/template/src/modules/auth/database/models/user.ts +46 -0
  161. package/template/src/modules/auth/database/seeders/role.ts +19 -0
  162. package/template/src/modules/auth/database/seeders/user.ts +33 -0
  163. package/template/src/modules/auth/jobs/forgetpass.ts +18 -0
  164. package/template/src/modules/auth/jobs/registeruser.ts +31 -0
  165. package/template/src/modules/auth/jobs/verifyemail.ts +18 -0
  166. package/template/src/modules/auth/routes/api.ts +151 -0
  167. package/template/src/modules/auth/routes/role.ts +39 -0
  168. package/template/src/modules/welcome/controllers/welcome.controller.ts +14 -0
  169. package/template/src/modules/welcome/controllers/welcome.schema.ts +6 -0
  170. package/template/src/modules/welcome/database/models/welcome.ts +6 -0
  171. package/template/src/modules/welcome/routes/api.ts +20 -0
  172. package/template/src/resources/index.html +16 -0
  173. package/template/src/resources/src/App.vue +5 -0
  174. package/template/src/resources/src/assets/css/styles.css +14934 -0
  175. package/template/src/resources/src/assets/css/styles.css.map +1 -0
  176. package/template/src/resources/src/assets/images/favicon/favicon.ico +0 -0
  177. package/template/src/resources/src/assets/images/favicon/favicon1.ico +0 -0
  178. package/template/src/resources/src/assets/images/logo-1.png +0 -0
  179. package/template/src/resources/src/assets/images/logo-dark-sm.png +0 -0
  180. package/template/src/resources/src/assets/images/logo-dark.png +0 -0
  181. package/template/src/resources/src/assets/images/logo-dark1.png +0 -0
  182. package/template/src/resources/src/assets/images/logo-sm.png +0 -0
  183. package/template/src/resources/src/assets/images/logo1.png +0 -0
  184. package/template/src/resources/src/assets/images/logo2.png +0 -0
  185. package/template/src/resources/src/assets/scss/custom.css +217 -0
  186. package/template/src/resources/src/assets/scss/custom.css.map +1 -0
  187. package/template/src/resources/src/assets/scss/custom.scss +1100 -0
  188. package/template/src/resources/src/components/Button.vue +35 -0
  189. package/template/src/resources/src/components/Checkbox.vue +29 -0
  190. package/template/src/resources/src/components/FloatButton.vue +36 -0
  191. package/template/src/resources/src/components/Href.vue +32 -0
  192. package/template/src/resources/src/components/Input.vue +227 -0
  193. package/template/src/resources/src/components/InputGroup.vue +153 -0
  194. package/template/src/resources/src/components/InputPasswordToggle.vue +226 -0
  195. package/template/src/resources/src/components/Modal.vue +102 -0
  196. package/template/src/resources/src/components/Pagebar.vue +28 -0
  197. package/template/src/resources/src/components/Refresh.vue +26 -0
  198. package/template/src/resources/src/components/Select.vue +390 -0
  199. package/template/src/resources/src/components/Spinner.vue +42 -0
  200. package/template/src/resources/src/components/Switch.vue +65 -0
  201. package/template/src/resources/src/components/TextArea.vue +121 -0
  202. package/template/src/resources/src/components/Toast.vue +56 -0
  203. package/template/src/resources/src/components/datatable/DataTableSkeleton.vue +99 -0
  204. package/template/src/resources/src/components/datatable/Pagination.vue +161 -0
  205. package/template/src/resources/src/components/datatable/SelectOpption.vue +54 -0
  206. package/template/src/resources/src/components/datatable/index.vue +237 -0
  207. package/template/src/resources/src/composables/useAuth.ts +52 -0
  208. package/template/src/resources/src/composables/useBrowserDetect.ts +5 -0
  209. package/template/src/resources/src/composables/useDialog.ts +5 -0
  210. package/template/src/resources/src/composables/useGum.ts +3 -0
  211. package/template/src/resources/src/composables/usePulse.ts +5 -0
  212. package/template/src/resources/src/env.d.ts +20 -0
  213. package/template/src/resources/src/helpers/nformatter.ts +10 -0
  214. package/template/src/resources/src/helpers/utils.ts +68 -0
  215. package/template/src/resources/src/layouts/AuthLayout.vue +20 -0
  216. package/template/src/resources/src/layouts/Layout/Footer.vue +23 -0
  217. package/template/src/resources/src/layouts/Layout/Header.vue +90 -0
  218. package/template/src/resources/src/layouts/Layout/Sidebar.vue +137 -0
  219. package/template/src/resources/src/layouts/Layout/index.vue +76 -0
  220. package/template/src/resources/src/main.ts +27 -0
  221. package/template/src/resources/src/pages/auth/forgetPassword.vue +76 -0
  222. package/template/src/resources/src/pages/auth/login.vue +93 -0
  223. package/template/src/resources/src/pages/auth/register.vue +130 -0
  224. package/template/src/resources/src/pages/auth/resetPassword.vue +119 -0
  225. package/template/src/resources/src/pages/auth/verifyEmail.vue +60 -0
  226. package/template/src/resources/src/pages/dashboard/index.vue +76 -0
  227. package/template/src/resources/src/plugins/axios.ts +33 -0
  228. package/template/src/resources/src/plugins/browserDetect.ts +55 -0
  229. package/template/src/resources/src/plugins/dialog.ts +167 -0
  230. package/template/src/resources/src/plugins/gum.ts +343 -0
  231. package/template/src/resources/src/plugins/pulse.ts +141 -0
  232. package/template/src/resources/src/plugins/routeProgress.ts +87 -0
  233. package/template/src/resources/src/router/index.ts +85 -0
  234. package/template/src/resources/src/stores/admin-ui.ts +148 -0
  235. package/template/src/resources/src/stores/auth.ts +151 -0
  236. package/template/src/resources/tsconfig.json +19 -0
  237. package/template/src/resources/vite.config.ts +43 -0
  238. package/template/src/storage/logs/app.log +20179 -0
  239. package/template/src/storage/logs/fatal.log +727 -0
  240. package/template/tsconfig.json +20 -0
@@ -0,0 +1,78 @@
1
+ import type { Context } from "hono";
2
+ import { deleteCookie, getCookie, setCookie } from "hono/cookie";
3
+ import { env } from "@/env.js";
4
+
5
+ const baseOptions = {
6
+ httpOnly: true,
7
+ secure: false,
8
+ sameSite: "Lax" as const,
9
+ path: "/"
10
+ };
11
+
12
+ export const cookie = {
13
+ /**
14
+ * Why: Stores short-lived access token cookie for authenticated requests.
15
+ * When: Login/register/refresh responses.
16
+ * Where: Auth controllers/helpers.
17
+ * How: Sets httpOnly cookie with configured access expiry.
18
+ */
19
+ setAuth(c: Context, token: string) {
20
+ setCookie(c, `${env.COOKIE_NAME}_access`, token, {
21
+ ...baseOptions,
22
+ maxAge: env.JWT_ACCESS_EXPIRY
23
+ });
24
+ },
25
+
26
+ /**
27
+ * Why: Stores refresh token cookie for token rotation.
28
+ * When: Login/register/refresh responses.
29
+ * Where: Auth controllers/helpers.
30
+ * How: Sets httpOnly cookie with explicit or default refresh maxAge.
31
+ */
32
+ setRefresh(c: Context, token: string, maxAge?: number) {
33
+ setCookie(c, `${env.COOKIE_NAME}_refresh`, token, {
34
+ ...baseOptions,
35
+ maxAge: typeof maxAge === "number" ? maxAge : env.JWT_REFRESH_EXPIRY
36
+ });
37
+ },
38
+
39
+ /**
40
+ * Why: Reads access token cookie from request context.
41
+ * When: Auth middleware validates request identity.
42
+ * Where: Middleware and auth helpers.
43
+ * How: Resolves cookie by configured access cookie name.
44
+ */
45
+ getAuth(c: Context) {
46
+ return getCookie(c, `${env.COOKIE_NAME}_access`);
47
+ },
48
+
49
+ /**
50
+ * Why: Reads refresh token cookie from request context.
51
+ * When: Refresh and logout flows.
52
+ * Where: Auth middleware/controllers.
53
+ * How: Resolves cookie by configured refresh cookie name.
54
+ */
55
+ getRefresh(c: Context) {
56
+ return getCookie(c, `${env.COOKIE_NAME}_refresh`);
57
+ },
58
+
59
+ /**
60
+ * Why: Clears access token cookie.
61
+ * When: Logout or invalid token handling.
62
+ * Where: Auth middleware/controllers.
63
+ * How: Deletes cookie path-scoped to root.
64
+ */
65
+ deleteAuth(c: Context) {
66
+ deleteCookie(c, `${env.COOKIE_NAME}_access`, { path: "/" });
67
+ },
68
+
69
+ /**
70
+ * Why: Clears refresh token cookie.
71
+ * When: Logout or invalid token handling.
72
+ * Where: Auth middleware/controllers.
73
+ * How: Deletes cookie path-scoped to root.
74
+ */
75
+ deleteRefresh(c: Context) {
76
+ deleteCookie(c, `${env.COOKIE_NAME}_refresh`, { path: "/" });
77
+ }
78
+ };
@@ -0,0 +1,45 @@
1
+ import { sign, verify } from "hono/jwt";
2
+ import { randomUUID } from "node:crypto";
3
+ import { env } from "@/env.js";
4
+
5
+ export const jwt = {
6
+ /**
7
+ * Why: Creates signed JWT access/refresh tokens with project claims.
8
+ * When: Auth flows issue or rotate credentials.
9
+ * Where: Auth controllers/helpers.
10
+ * How: Builds payload with iat/exp/type (+jti for refresh) and signs HS256.
11
+ */
12
+ async generateToken(payload: any, type: "access" | "refresh", expirySeconds?: number) {
13
+ const now = Math.floor(Date.now() / 1000);
14
+ const exp = now
15
+ + (typeof expirySeconds === "number"
16
+ ? expirySeconds
17
+ : type === "refresh"
18
+ ? env.JWT_REFRESH_EXPIRY
19
+ : env.JWT_ACCESS_EXPIRY);
20
+ const secret = type === "refresh" ? env.JWT_REFRESH_SECRET : env.JWT_ACCESS_SECRET;
21
+ const jti = type === "refresh" ? randomUUID() : undefined;
22
+
23
+ const tokenPayload = { ...payload, type, iat: now, exp, ...(jti ? { jti } : {}) };
24
+ const token = await sign(tokenPayload, secret, "HS256");
25
+
26
+ return { token, jti, exp };
27
+ },
28
+
29
+ /**
30
+ * Why: Validates JWT and enforces token type.
31
+ * When: Middleware and refresh flows verify credentials.
32
+ * Where: Auth and realtime handshake.
33
+ * How: Verifies signature/expiry with type-specific secret and checks payload type.
34
+ */
35
+ async verifyToken(token: string, type: "access" | "refresh") {
36
+ try {
37
+ const secret = type === "refresh" ? env.JWT_REFRESH_SECRET : env.JWT_ACCESS_SECRET;
38
+ const payload = await verify(token, secret, "HS256");
39
+ return payload.type === type ? payload : null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+ };
45
+
@@ -0,0 +1,35 @@
1
+ const shutdownSignals = ["SIGINT", "SIGTERM"] as const;
2
+ export type ShutdownSignal = (typeof shutdownSignals)[number];
3
+
4
+ type ShutdownHandler = (signal: ShutdownSignal) => Promise<void>;
5
+
6
+ /**
7
+ * Why: Avoid duplicated graceful-shutdown wiring across runtime entrypoints.
8
+ * When: Server/worker/scheduler need signal handling.
9
+ * Where: Runtime bootstrap files.
10
+ * How: Registers SIGINT/SIGTERM handlers and forwards signal to async callback.
11
+ */
12
+ export function registerShutdownSignals(handler: ShutdownHandler) {
13
+ for (const signal of shutdownSignals) {
14
+ process.on(signal, () => {
15
+ void handler(signal);
16
+ });
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Why: Keep comma-separated CLI/env parsing consistent.
22
+ * When: Runtime options are provided as a single CSV string.
23
+ * Where: Worker and other entrypoints.
24
+ * How: Trims tokens, removes blanks, and falls back when empty.
25
+ */
26
+ export function parseCsvOrFallback(value: string | undefined, fallback: string[]) {
27
+ if (!value) return fallback;
28
+
29
+ const parsed = value
30
+ .split(",")
31
+ .map((item) => item.trim())
32
+ .filter(Boolean);
33
+
34
+ return parsed.length ? parsed : fallback;
35
+ }
@@ -0,0 +1,102 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import winston from "winston";
4
+ import { env } from "@/env.js";
5
+
6
+ const logDir = path.resolve(process.cwd(), "src", "storage", "logs");
7
+
8
+ if (!fs.existsSync(logDir)) {
9
+ fs.mkdirSync(logDir, { recursive: true });
10
+ }
11
+
12
+ function visibleMeta(meta: Record<string, unknown>) {
13
+ const { service: _service, ...rest } = meta;
14
+ return rest;
15
+ }
16
+
17
+ function normalizeMeta(meta: Record<string, unknown>) {
18
+ const next: Record<string, unknown> = { ...meta };
19
+
20
+ const processMeta = next.process as Record<string, unknown> | undefined;
21
+ if (processMeta && typeof processMeta === "object") {
22
+ const memoryUsage = processMeta.memoryUsage as Record<string, unknown> | undefined;
23
+ if (memoryUsage && typeof memoryUsage === "object") {
24
+ processMeta.memoryUsage = Object.entries(memoryUsage)
25
+ .map(([key, value]) => `${key}=${value}`)
26
+ .join(" ");
27
+ }
28
+ if (Array.isArray(processMeta.argv)) {
29
+ processMeta.argv = `[${processMeta.argv.join(" ")}]`;
30
+ }
31
+ next.process = processMeta;
32
+ }
33
+
34
+ const trace = next.trace;
35
+ if (Array.isArray(trace) && trace.length > 3) {
36
+ next.trace = [...trace.slice(0, 3), `... ${trace.length - 3} more frame(s)`];
37
+ }
38
+
39
+ const osMeta = next.os as Record<string, unknown> | undefined;
40
+ if (osMeta && typeof osMeta === "object" && Array.isArray(osMeta.loadavg)) {
41
+ osMeta.loadavg = `[${osMeta.loadavg.join(",")}]`;
42
+ next.os = osMeta;
43
+ }
44
+
45
+ return next;
46
+ }
47
+
48
+ const fileFormat = winston.format.combine(
49
+ winston.format.timestamp({ format: "HH:mm:ss" }),
50
+ winston.format.printf(({ timestamp, level, message, stack, ...meta }) => {
51
+ const rest = normalizeMeta(visibleMeta(meta));
52
+ const metaString = Object.keys(rest).length ? `\n${JSON.stringify(rest, null, 2)}` : "";
53
+ return `[${timestamp}] ${level} -> ${stack || message}${metaString}\n`;
54
+ })
55
+ );
56
+
57
+ const consoleFormat = winston.format.combine(
58
+ winston.format.colorize(),
59
+ winston.format.timestamp({ format: "HH:mm:ss" }),
60
+ winston.format.printf(({ timestamp, level, message, stack, ...meta }) => {
61
+ const rest = normalizeMeta(visibleMeta(meta));
62
+ const metaString = Object.keys(rest).length ? `\n${JSON.stringify(rest, null, 2)}` : "";
63
+ return `[${timestamp}] ${level} -> ${stack || message}${metaString}`;
64
+ })
65
+ );
66
+
67
+ /**
68
+ * Why: Shared structured logger for app/runtime/framework internals.
69
+ * When: Any code needs operational logs or error reporting.
70
+ * Where: Imported across framework and modules.
71
+ * How: Writes color console logs + rotating files with exception/rejection handlers.
72
+ */
73
+ export const logger = winston.createLogger({
74
+ level: env.LOG_LEVEL || "info",
75
+ defaultMeta: {
76
+ service: env.APP_NAME
77
+ },
78
+ transports: [
79
+ new winston.transports.Console({
80
+ level: env.LOG_LEVEL || "debug",
81
+ format: consoleFormat
82
+ }),
83
+ new winston.transports.File({
84
+ filename: path.join(logDir, "app.log"),
85
+ level: "info",
86
+ maxsize: 10 * 1024 * 1024,
87
+ maxFiles: 5,
88
+ tailable: true,
89
+ format: fileFormat
90
+ }),
91
+ new winston.transports.File({
92
+ filename: path.join(logDir, "fatal.log"),
93
+ level: "error",
94
+ maxsize: 10 * 1024 * 1024,
95
+ maxFiles: 3,
96
+ tailable: true,
97
+ handleExceptions: true,
98
+ handleRejections: true,
99
+ format: fileFormat
100
+ })
101
+ ]
102
+ });
@@ -0,0 +1,43 @@
1
+ import nodemailer from "nodemailer";
2
+ import { env } from "@/env.js";
3
+ import { logger } from "@/framework/support/logger.js";
4
+
5
+ type MailPayload = {
6
+ to: string;
7
+ subject: string;
8
+ html?: string;
9
+ text?: string;
10
+ };
11
+
12
+ const transport = nodemailer.createTransport({
13
+ host: env.MAIL_HOST,
14
+ port: env.MAIL_PORT,
15
+ secure: false,
16
+ auth: env.MAIL_USERNAME ? { user: env.MAIL_USERNAME, pass: env.MAIL_PASSWORD } : undefined
17
+ });
18
+
19
+ export const mail = {
20
+ /**
21
+ * Why: Sends transactional email through configured SMTP transport.
22
+ * When: Features need notifications/password reset/signup email.
23
+ * Where: Jobs and event handlers.
24
+ * How: Uses nodemailer transport and respects MAIL_FAIL_SILENT behavior.
25
+ */
26
+ async sendMail(payload: MailPayload) {
27
+ try {
28
+ return await transport.sendMail({
29
+ from: env.MAIL_FROM_ADDRESS,
30
+ ...payload
31
+ });
32
+ } catch (error) {
33
+ logger.error("Mail send failed", {
34
+ to: payload.to,
35
+ subject: payload.subject,
36
+ error: error instanceof Error ? error.message : error
37
+ });
38
+
39
+ if (!env.MAIL_FAIL_SILENT) throw error;
40
+ return null;
41
+ }
42
+ }
43
+ };
@@ -0,0 +1,23 @@
1
+ import bcrypt from "bcryptjs";
2
+
3
+ export const password = {
4
+ /**
5
+ * Why: Hashes plaintext password before persistence.
6
+ * When: Registration and password reset flows.
7
+ * Where: Auth controllers/seeders.
8
+ * How: Uses bcrypt with cost factor 10.
9
+ */
10
+ async hashPassword(inputPassword: string) {
11
+ return await bcrypt.hash(inputPassword, 10);
12
+ },
13
+
14
+ /**
15
+ * Why: Compares plaintext password to stored hash.
16
+ * When: Login/authentication checks.
17
+ * Where: Auth controllers/middleware.
18
+ * How: Uses bcrypt secure compare.
19
+ */
20
+ async verifyPassword(inputPassword: string, hashedPassword: string) {
21
+ return await bcrypt.compare(inputPassword, hashedPassword);
22
+ }
23
+ };
@@ -0,0 +1,25 @@
1
+ import { env } from "@/env.js";
2
+
3
+ export const urls = {
4
+ /**
5
+ * Why: Returns normalized app base URL without trailing slash.
6
+ * When: Building absolute links.
7
+ * Where: Mail/reset URL generation.
8
+ * How: Trims trailing slash from APP_URL.
9
+ */
10
+ appUrl() {
11
+ return env.APP_URL.replace(/\/$/, "");
12
+ },
13
+
14
+ /**
15
+ * Why: Builds absolute URL from APP_URL and a path.
16
+ * When: Creating links for emails or external callbacks.
17
+ * Where: Auth/password reset and integrations.
18
+ * How: Normalizes slash joining between base and path.
19
+ */
20
+ url(path = "") {
21
+ const base = urls.appUrl();
22
+ if (!path) return base;
23
+ return `${base}${path.startsWith("/") ? path : `/${path}`}`;
24
+ }
25
+ };
@@ -0,0 +1,98 @@
1
+ import type { Context, Next } from "hono";
2
+ import { eq } from "drizzle-orm";
3
+ import { cookie, db, jwt } from "@/framework/facade.js";
4
+ import { refreshTokens, users } from "@/modules/auth/database/models/user.js";
5
+
6
+ export async function authMiddleware(c: Context, next: Next) {
7
+ const accessToken = cookie.getAuth(c);
8
+
9
+ if (accessToken) {
10
+ const accessPayload = await jwt.verifyToken(accessToken, "access");
11
+
12
+ if (accessPayload) {
13
+ const user = await db.query.users.findFirst({
14
+ where: eq(users.id, accessPayload.id as number),
15
+ with: { role: true }
16
+ });
17
+
18
+ if (!user) {
19
+ cookie.deleteAuth(c);
20
+ cookie.deleteRefresh(c);
21
+ return c.json({ message: "Unauthorized" }, 401);
22
+ }
23
+
24
+ c.set("auth", {
25
+ ...accessPayload,
26
+ id: user.id,
27
+ email: user.email,
28
+ roleId: user.role?.id ?? null,
29
+ role: user.role?.name ?? null
30
+ });
31
+ return await next();
32
+ }
33
+ }
34
+
35
+ const refreshToken = cookie.getRefresh(c);
36
+
37
+ if (!refreshToken) {
38
+ cookie.deleteAuth(c);
39
+ cookie.deleteRefresh(c);
40
+ return c.json({ message: "Unauthorized" }, 401);
41
+ }
42
+
43
+ const refreshPayload = await jwt.verifyToken(refreshToken, "refresh");
44
+
45
+ if (!refreshPayload?.jti) {
46
+ cookie.deleteAuth(c);
47
+ cookie.deleteRefresh(c);
48
+ return c.json({ message: "Invalid token" }, 401);
49
+ }
50
+
51
+ const storedToken = await db.query.refreshTokens.findFirst({
52
+ where: eq(refreshTokens.jti, refreshPayload.jti as string)
53
+ });
54
+
55
+ if (!storedToken || storedToken.revoked === 1) {
56
+ cookie.deleteAuth(c);
57
+ cookie.deleteRefresh(c);
58
+ return c.json({ message: "Invalid token" }, 401);
59
+ }
60
+
61
+ if (storedToken.expiresAt.getTime() < Date.now()) {
62
+ await db.delete(refreshTokens).where(eq(refreshTokens.id, storedToken.id));
63
+ cookie.deleteAuth(c);
64
+ cookie.deleteRefresh(c);
65
+ return c.json({ message: "Invalid token" }, 401);
66
+ }
67
+
68
+ const user = await db.query.users.findFirst({
69
+ where: eq(users.id, refreshPayload.id as number),
70
+ with: { role: true }
71
+ });
72
+
73
+ if (!user) {
74
+ cookie.deleteAuth(c);
75
+ cookie.deleteRefresh(c);
76
+ return c.json({ message: "Unauthorized" }, 401);
77
+ }
78
+
79
+ const newAccessToken = await jwt.generateToken(
80
+ {
81
+ id: user.id,
82
+ email: user.email,
83
+ roleId: user.role?.id ?? null,
84
+ role: user.role?.name ?? null
85
+ },
86
+ "access"
87
+ );
88
+
89
+ cookie.setAuth(c, newAccessToken.token);
90
+ c.set("auth", {
91
+ id: user.id,
92
+ email: user.email,
93
+ roleId: user.role?.id ?? null,
94
+ role: user.role?.name ?? null,
95
+ type: "access"
96
+ });
97
+ return await next();
98
+ }
@@ -0,0 +1,24 @@
1
+ import type { Context, Next } from "hono";
2
+
3
+ type AuthPayload = {
4
+ role?: string | null;
5
+ };
6
+
7
+ export function requireRole(...allowedRoles: string[]) {
8
+ const roles = allowedRoles.map((role) => role.toLowerCase());
9
+
10
+ return async (c: Context, next: Next) => {
11
+ const auth = c.get("auth") as AuthPayload | undefined;
12
+
13
+ if (!auth) {
14
+ return c.json({ message: "Unauthorized" }, 401);
15
+ }
16
+
17
+ const userRole = String(auth.role ?? "").toLowerCase();
18
+ if (!roles.includes(userRole)) {
19
+ return c.json({ message: "Forbidden: Insufficient permissions" }, 403);
20
+ }
21
+
22
+ return await next();
23
+ };
24
+ }