create-bubbles 0.1.22 → 0.1.24

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 (281) hide show
  1. package/dist/index.mjs +5 -0
  2. package/index.js +1 -1
  3. package/package.json +25 -24
  4. package/template-taro-react-oxc/.env.development +1 -1
  5. package/template-taro-react-oxc/.env.production +1 -1
  6. package/template-taro-react-oxc/project.config.json +1 -1
  7. package/template-taro-vue-eslint/project.config.json +2 -2
  8. package/{template-vp-monorepo-react-nestjs → template-vp-monorepo-react-hono}/.spaces/server//347/231/273/345/275/225/JWT/344/270/216RefreshToken/350/275/256/346/215/242/351/211/264/346/235/203/346/236/266/346/236/204/345/256/236/347/216/260/346/214/207/345/215/227.md +588 -1133
  9. package/template-vp-monorepo-react-hono/.vite-hooks/commit-msg +2 -0
  10. package/template-vp-monorepo-react-hono/.vite-hooks/pre-commit +2 -0
  11. package/template-vp-monorepo-react-hono/.vscode/extensions.json +3 -0
  12. package/template-vp-monorepo-react-hono/.vscode/settings.json +23 -0
  13. package/template-vp-monorepo-react-hono/AGENTS.md +16 -0
  14. package/template-vp-monorepo-react-hono/README.md +54 -0
  15. package/template-vp-monorepo-react-hono/apps/server/.env.example +13 -0
  16. package/template-vp-monorepo-react-hono/apps/server/README.md +75 -0
  17. package/template-vp-monorepo-react-hono/apps/server/drizzle/0000_flaky_madame_masque.sql +18 -0
  18. package/template-vp-monorepo-react-hono/apps/server/drizzle/meta/0000_snapshot.json +153 -0
  19. package/template-vp-monorepo-react-hono/apps/server/drizzle/meta/_journal.json +13 -0
  20. package/template-vp-monorepo-react-hono/apps/server/drizzle.config.ts +19 -0
  21. package/template-vp-monorepo-react-hono/apps/server/package.json +46 -0
  22. package/template-vp-monorepo-react-hono/apps/server/src/app.test.ts +57 -0
  23. package/template-vp-monorepo-react-hono/apps/server/src/app.ts +68 -0
  24. package/template-vp-monorepo-react-hono/apps/server/src/common/errors/app-error.ts +23 -0
  25. package/template-vp-monorepo-react-hono/apps/server/src/common/http/response.ts +21 -0
  26. package/template-vp-monorepo-react-hono/apps/server/src/common/logger.ts +30 -0
  27. package/template-vp-monorepo-react-hono/apps/server/src/config/env.ts +54 -0
  28. package/template-vp-monorepo-react-hono/apps/server/src/index.bun.ts +60 -0
  29. package/template-vp-monorepo-react-hono/apps/server/src/index.ts +73 -0
  30. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/database/client.ts +46 -0
  31. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/database/schema/index.ts +1 -0
  32. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/database/schema/job-runs.ts +42 -0
  33. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/queue/connection.ts +6 -0
  34. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/redis/client.ts +39 -0
  35. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/redis/options.ts +28 -0
  36. package/template-vp-monorepo-react-hono/apps/server/src/modules/health/health.routes.ts +17 -0
  37. package/template-vp-monorepo-react-hono/apps/server/src/modules/health/health.service.ts +59 -0
  38. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.queue.ts +46 -0
  39. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.repository.ts +101 -0
  40. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.routes.ts +46 -0
  41. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.schemas.ts +9 -0
  42. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.service.ts +57 -0
  43. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.types.ts +11 -0
  44. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.worker.ts +108 -0
  45. package/template-vp-monorepo-react-hono/apps/server/src/runtime/shutdown.ts +38 -0
  46. package/template-vp-monorepo-react-hono/apps/server/src/worker.ts +48 -0
  47. package/template-vp-monorepo-react-hono/apps/server/tsconfig.json +15 -0
  48. package/template-vp-monorepo-react-hono/apps/server/vite.config.ts +15 -0
  49. package/template-vp-monorepo-react-hono/apps/web/.env +2 -0
  50. package/template-vp-monorepo-react-hono/apps/web/.env.dev +1 -0
  51. package/template-vp-monorepo-react-hono/apps/web/.env.prod +1 -0
  52. package/template-vp-monorepo-react-hono/apps/web/index.html +13 -0
  53. package/template-vp-monorepo-react-hono/apps/web/package.json +35 -0
  54. package/template-vp-monorepo-react-hono/apps/web/public/favicon.svg +1 -0
  55. package/template-vp-monorepo-react-hono/apps/web/public/icons.svg +24 -0
  56. package/template-vp-monorepo-react-hono/apps/web/src/App.tsx +25 -0
  57. package/template-vp-monorepo-react-hono/apps/web/src/assets/hero.png +0 -0
  58. package/template-vp-monorepo-react-hono/apps/web/src/assets/react.svg +1 -0
  59. package/template-vp-monorepo-react-hono/apps/web/src/assets/vite.svg +1 -0
  60. package/template-vp-monorepo-react-hono/apps/web/src/components/Loading/PageLoading.tsx +11 -0
  61. package/template-vp-monorepo-react-hono/apps/web/src/locales/en_US.json +4 -0
  62. package/template-vp-monorepo-react-hono/apps/web/src/locales/zh_CN.json +4 -0
  63. package/template-vp-monorepo-react-hono/apps/web/src/main.tsx +5 -0
  64. package/template-vp-monorepo-react-hono/apps/web/src/pages/home/index.tsx +47 -0
  65. package/template-vp-monorepo-react-hono/apps/web/src/router/index.tsx +25 -0
  66. package/template-vp-monorepo-react-hono/apps/web/src/router/modules/.gitkeep +0 -0
  67. package/template-vp-monorepo-react-hono/apps/web/src/styles/index.css +7 -0
  68. package/template-vp-monorepo-react-hono/apps/web/src/types/auto-imports.d.ts +58 -0
  69. package/template-vp-monorepo-react-hono/apps/web/src/utils/env/index.ts +6 -0
  70. package/template-vp-monorepo-react-hono/apps/web/src/utils/request/core/index.ts +420 -0
  71. package/template-vp-monorepo-react-hono/apps/web/src/utils/request/core/utils.ts +39 -0
  72. package/template-vp-monorepo-react-hono/apps/web/src/utils/request/index.ts +61 -0
  73. package/template-vp-monorepo-react-hono/apps/web/tsconfig.json +18 -0
  74. package/template-vp-monorepo-react-hono/apps/web/vite.config.ts +45 -0
  75. package/template-vp-monorepo-react-hono/commitlint.config.js +1 -0
  76. package/template-vp-monorepo-react-hono/docker-compose.yml +41 -0
  77. package/template-vp-monorepo-react-hono/mise.toml +4 -0
  78. package/template-vp-monorepo-react-hono/package.json +37 -0
  79. package/template-vp-monorepo-react-hono/packages/i18n/core/package.json +20 -0
  80. package/template-vp-monorepo-react-hono/packages/i18n/core/src/format.ts +9 -0
  81. package/template-vp-monorepo-react-hono/packages/i18n/core/src/i18n.ts +73 -0
  82. package/template-vp-monorepo-react-hono/packages/i18n/core/src/index.ts +24 -0
  83. package/template-vp-monorepo-react-hono/packages/i18n/core/src/storage.ts +47 -0
  84. package/template-vp-monorepo-react-hono/packages/i18n/core/src/store.ts +48 -0
  85. package/template-vp-monorepo-react-hono/packages/i18n/core/src/types.ts +36 -0
  86. package/template-vp-monorepo-react-hono/packages/i18n/core/tsconfig.json +4 -0
  87. package/template-vp-monorepo-react-hono/packages/i18n/core/vite.config.ts +13 -0
  88. package/template-vp-monorepo-react-hono/packages/i18n/react/package.json +28 -0
  89. package/template-vp-monorepo-react-hono/packages/i18n/react/src/index.tsx +26 -0
  90. package/template-vp-monorepo-react-hono/packages/i18n/react/src/use-store.ts +14 -0
  91. package/template-vp-monorepo-react-hono/packages/i18n/react/tsconfig.json +9 -0
  92. package/template-vp-monorepo-react-hono/packages/i18n/react/vite.config.ts +16 -0
  93. package/template-vp-monorepo-react-hono/packages/i18n/vue/package.json +27 -0
  94. package/template-vp-monorepo-react-hono/packages/i18n/vue/src/index.ts +52 -0
  95. package/template-vp-monorepo-react-hono/packages/i18n/vue/src/use-store.ts +19 -0
  96. package/template-vp-monorepo-react-hono/packages/i18n/vue/tsconfig.json +8 -0
  97. package/template-vp-monorepo-react-hono/packages/i18n/vue/vite.config.ts +16 -0
  98. package/template-vp-monorepo-react-hono/pnpm-workspace.yaml +57 -0
  99. package/template-vp-monorepo-react-hono/scripts/clean.mjs +48 -0
  100. package/template-vp-monorepo-react-hono/tsconfig.base.json +10 -0
  101. package/template-vp-monorepo-react-hono/tsconfig.bundler.json +12 -0
  102. package/template-vp-monorepo-react-hono/tsconfig.json +11 -0
  103. package/template-vp-monorepo-react-hono/tsconfig.lib.json +10 -0
  104. package/template-vp-monorepo-react-hono/vite.config.ts +20 -0
  105. package/template-vp-monorepo-react-nestjs/.spaces/server/todo.md +991 -0
  106. package/template-vp-monorepo-react-nestjs/.vscode/launch.json +16 -0
  107. package/template-vp-monorepo-react-nestjs/.vscode/settings.json +21 -17
  108. package/template-vp-monorepo-react-nestjs/apps/server/.env.development +3 -39
  109. package/template-vp-monorepo-react-nestjs/apps/server/.swcrc +8 -0
  110. package/template-vp-monorepo-react-nestjs/apps/server/drizzle.config.ts +8 -3
  111. package/template-vp-monorepo-react-nestjs/apps/server/nest-cli.json +8 -1
  112. package/template-vp-monorepo-react-nestjs/apps/server/package.json +11 -6
  113. package/template-vp-monorepo-react-nestjs/apps/server/src/app.controller.ts +2 -0
  114. package/template-vp-monorepo-react-nestjs/apps/server/src/app.module.ts +20 -15
  115. package/template-vp-monorepo-react-nestjs/apps/server/src/common/adapters/fastify.adapter.ts +16 -0
  116. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/auth.ts +2 -0
  117. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/response.ts +0 -4
  118. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/session.constants.ts +28 -0
  119. package/template-vp-monorepo-react-nestjs/apps/server/src/common/decorators/current-auth.decorator.ts +16 -0
  120. package/template-vp-monorepo-react-nestjs/apps/server/src/common/decorators/public.decorator.ts +4 -0
  121. package/template-vp-monorepo-react-nestjs/apps/server/src/common/dto/api-failure.dto.ts +25 -0
  122. package/template-vp-monorepo-react-nestjs/apps/server/src/common/error/common.error.ts +40 -0
  123. package/template-vp-monorepo-react-nestjs/apps/server/src/common/exceptions/app.exception.ts +55 -0
  124. package/template-vp-monorepo-react-nestjs/apps/server/src/common/filters/global-exception.filter.ts +384 -0
  125. package/template-vp-monorepo-react-nestjs/apps/server/src/common/openapi/response-headers.ts +9 -0
  126. package/template-vp-monorepo-react-nestjs/apps/server/src/config/index.ts +1 -1
  127. package/template-vp-monorepo-react-nestjs/apps/server/src/config/session.config.ts +36 -0
  128. package/template-vp-monorepo-react-nestjs/apps/server/src/database/schema.ts +7 -8
  129. package/template-vp-monorepo-react-nestjs/apps/server/src/main.ts +29 -8
  130. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.controller.ts +55 -0
  131. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.errors.ts +42 -0
  132. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.module.ts +26 -0
  133. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.repository.ts +40 -0
  134. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.service.ts +129 -0
  135. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/auth-repsponse.dto.ts +31 -0
  136. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/login.dto.ts +11 -0
  137. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/register.dto.ts +17 -0
  138. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/guards/session-auth.guard.ts +50 -0
  139. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/password.service.ts +24 -0
  140. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session-store.service.ts +198 -0
  141. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session-token.service.ts +29 -0
  142. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session.script.ts +221 -0
  143. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session.types.ts +24 -0
  144. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/db/db.controller.ts +1 -1
  145. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/db/dto/create-user.dto.ts +3 -5
  146. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/redis/redis.service.ts +1 -1
  147. package/template-vp-monorepo-react-nestjs/apps/server/src/utils/env-arr.ts +3 -0
  148. package/template-vp-monorepo-react-nestjs/apps/server/test/error-catalog.spec.ts +30 -0
  149. package/template-vp-monorepo-react-nestjs/apps/server/tsconfig.build.json +11 -1
  150. package/template-vp-monorepo-react-nestjs/apps/server/tsconfig.json +12 -20
  151. package/template-vp-monorepo-react-nestjs/apps/server/vite.config.ts +15 -0
  152. package/template-vp-monorepo-react-nestjs/apps/web/package.json +1 -0
  153. package/template-vp-monorepo-react-nestjs/apps/web/tsconfig.json +5 -15
  154. package/template-vp-monorepo-react-nestjs/apps/web-vue/commitlint.config.js +1 -1
  155. package/template-vp-monorepo-react-nestjs/apps/web-vue/lefthook.yml +1 -1
  156. package/template-vp-monorepo-react-nestjs/apps/web-vue/package.json +1 -3
  157. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/api/index.ts +5 -5
  158. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/components/Icon/svg-icon.vue +3 -3
  159. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/hooks/chart/lib.ts +6 -6
  160. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/hooks/chart/useEcharts.ts +34 -29
  161. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/i18n/instance.ts +2 -0
  162. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/layout/default/index.vue +17 -32
  163. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/locales/en_US.json +4 -1
  164. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/locales/zh_CN.json +4 -1
  165. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/guard/index.ts +3 -3
  166. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/guard/permissionGuard.ts +7 -7
  167. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/index.ts +7 -7
  168. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/interface.ts +6 -6
  169. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/store/index.ts +6 -6
  170. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/store/modules/user.ts +9 -9
  171. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/styles/index.scss +3 -3
  172. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/types/antdv-next.d.ts +1 -0
  173. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/env.ts +1 -1
  174. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/request/core/index.ts +63 -45
  175. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/request/core/utils.ts +12 -6
  176. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/request/index.ts +1 -1
  177. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/example/echart/config.ts +687 -687
  178. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/example/echart/index.vue +7 -7
  179. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/example/tree-chart.vue +2 -7
  180. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/home/index.vue +13 -3
  181. package/template-vp-monorepo-react-nestjs/apps/web-vue/tsconfig.json +5 -17
  182. package/template-vp-monorepo-react-nestjs/apps/web-vue/uno.config.ts +5 -5
  183. package/template-vp-monorepo-react-nestjs/i18n.config.ts +22 -0
  184. package/template-vp-monorepo-react-nestjs/package.json +12 -1
  185. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/README.md +113 -0
  186. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/docs/excel-sync-plan.md +618 -0
  187. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/package.json +31 -0
  188. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/cli.ts +5 -0
  189. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/command.ts +512 -0
  190. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/config.ts +15 -0
  191. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/files.ts +67 -0
  192. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/index.ts +19 -0
  193. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/load-config.ts +228 -0
  194. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/report.ts +57 -0
  195. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/scanner.test.ts +81 -0
  196. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/scanner.ts +290 -0
  197. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/sync.ts +274 -0
  198. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/command.test.ts +209 -0
  199. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/config.test.ts +19 -0
  200. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/load-config.test.ts +194 -0
  201. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/report.test.ts +63 -0
  202. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/sync.test.ts +203 -0
  203. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/temporary-directory.ts +25 -0
  204. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/tsconfig.json +9 -0
  205. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/vite.config.ts +18 -0
  206. package/template-vp-monorepo-react-nestjs/packages/i18n/core/tsconfig.json +1 -12
  207. package/template-vp-monorepo-react-nestjs/packages/i18n/react/tsconfig.json +3 -10
  208. package/template-vp-monorepo-react-nestjs/packages/i18n/vue/tsconfig.json +3 -10
  209. package/template-vp-monorepo-react-nestjs/packages/shared/package.json +40 -0
  210. package/template-vp-monorepo-react-nestjs/packages/shared/src/index.ts +13 -0
  211. package/template-vp-monorepo-react-nestjs/packages/shared/src/types/auth.ts +35 -0
  212. package/template-vp-monorepo-react-nestjs/packages/shared/src/types/common.ts +11 -0
  213. package/template-vp-monorepo-react-nestjs/packages/shared/src/types/index.ts +11 -0
  214. package/template-vp-monorepo-react-nestjs/packages/shared/src/utils/account.ts +5 -0
  215. package/template-vp-monorepo-react-nestjs/packages/shared/src/utils/index.ts +2 -0
  216. package/template-vp-monorepo-react-nestjs/packages/shared/src/utils/session-terminal.ts +11 -0
  217. package/template-vp-monorepo-react-nestjs/packages/shared/tsconfig.json +4 -0
  218. package/template-vp-monorepo-react-nestjs/packages/shared/vite.config.ts +17 -0
  219. package/template-vp-monorepo-react-nestjs/pnpm-workspace.yaml +6 -6
  220. package/template-vp-monorepo-react-nestjs/scripts/clean.mjs +48 -0
  221. package/template-vp-monorepo-react-nestjs/tsconfig.base.json +10 -0
  222. package/template-vp-monorepo-react-nestjs/tsconfig.bundler.json +12 -0
  223. package/template-vp-monorepo-react-nestjs/tsconfig.json +8 -6
  224. package/template-vp-monorepo-react-nestjs/tsconfig.lib.json +10 -0
  225. package/template-vp-monorepo-react-nestjs/vite.config.ts +2 -1
  226. package/template-vp-vue-eslint-vapor/.env +6 -0
  227. package/template-vp-vue-eslint-vapor/.env.development +2 -0
  228. package/template-vp-vue-eslint-vapor/.env.production +2 -0
  229. package/template-vp-vue-eslint-vapor/.vite-hooks/commit-msg +1 -0
  230. package/template-vp-vue-eslint-vapor/.vite-hooks/pre-commit +1 -0
  231. package/template-vp-vue-eslint-vapor/.vscode/extensions.json +3 -0
  232. package/template-vp-vue-eslint-vapor/AGENTS.md +16 -0
  233. package/template-vp-vue-eslint-vapor/README.md +93 -0
  234. package/template-vp-vue-eslint-vapor/commitlint.config.js +1 -0
  235. package/template-vp-vue-eslint-vapor/eslint.config.js +13 -0
  236. package/template-vp-vue-eslint-vapor/index.html +13 -0
  237. package/template-vp-vue-eslint-vapor/package.json +64 -0
  238. package/template-vp-vue-eslint-vapor/pnpm-workspace.yaml +2 -0
  239. package/template-vp-vue-eslint-vapor/public/vite.svg +1 -0
  240. package/template-vp-vue-eslint-vapor/src/App.vue +7 -0
  241. package/template-vp-vue-eslint-vapor/src/api/index.ts +12 -0
  242. package/template-vp-vue-eslint-vapor/src/assets/icon/home.svg +3 -0
  243. package/template-vp-vue-eslint-vapor/src/assets/icon/logo.svg +1 -0
  244. package/template-vp-vue-eslint-vapor/src/assets/image/.gitkeep +0 -0
  245. package/template-vp-vue-eslint-vapor/src/components/Icon/svg-icon.vue +29 -0
  246. package/template-vp-vue-eslint-vapor/src/hooks/chart/lib.ts +57 -0
  247. package/template-vp-vue-eslint-vapor/src/hooks/chart/useEcharts.ts +65 -0
  248. package/template-vp-vue-eslint-vapor/src/layout/default/header/index.vue +57 -0
  249. package/template-vp-vue-eslint-vapor/src/layout/default/index.vue +138 -0
  250. package/template-vp-vue-eslint-vapor/src/main.ts +19 -0
  251. package/template-vp-vue-eslint-vapor/src/router/guard/index.ts +7 -0
  252. package/template-vp-vue-eslint-vapor/src/router/guard/permissionGuard.ts +13 -0
  253. package/template-vp-vue-eslint-vapor/src/router/index.ts +18 -0
  254. package/template-vp-vue-eslint-vapor/src/router/interface.ts +9 -0
  255. package/template-vp-vue-eslint-vapor/src/router/modules/index.ts +33 -0
  256. package/template-vp-vue-eslint-vapor/src/store/index.ts +11 -0
  257. package/template-vp-vue-eslint-vapor/src/store/modules/user.ts +17 -0
  258. package/template-vp-vue-eslint-vapor/src/styles/font.scss +0 -0
  259. package/template-vp-vue-eslint-vapor/src/styles/index.scss +29 -0
  260. package/template-vp-vue-eslint-vapor/src/styles/variables.scss +3 -0
  261. package/template-vp-vue-eslint-vapor/src/types/vite-env.d.ts +15 -0
  262. package/template-vp-vue-eslint-vapor/src/utils/env.ts +11 -0
  263. package/template-vp-vue-eslint-vapor/src/utils/request/core/index.ts +425 -0
  264. package/template-vp-vue-eslint-vapor/src/utils/request/core/utils.ts +39 -0
  265. package/template-vp-vue-eslint-vapor/src/utils/request/index.ts +44 -0
  266. package/template-vp-vue-eslint-vapor/src/views/example/echart/config.ts +1794 -0
  267. package/template-vp-vue-eslint-vapor/src/views/example/echart/index.vue +20 -0
  268. package/template-vp-vue-eslint-vapor/src/views/example/tree-chart.vue +16 -0
  269. package/template-vp-vue-eslint-vapor/src/views/home/index.vue +16 -0
  270. package/template-vp-vue-eslint-vapor/src/views/login/index.vue +9 -0
  271. package/template-vp-vue-eslint-vapor/tsconfig.json +32 -0
  272. package/template-vp-vue-eslint-vapor/tsconfig.tsbuildinfo +1 -0
  273. package/template-vp-vue-eslint-vapor/uno.config.ts +17 -0
  274. package/template-vp-vue-eslint-vapor/vite.config.ts +66 -0
  275. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/decorator.ts +0 -2
  276. package/template-vp-monorepo-react-nestjs/apps/server/src/common/decorators/bypass.ts +0 -12
  277. package/template-vp-monorepo-react-nestjs/apps/server/src/common/interceptors/transform.ts +0 -34
  278. package/template-vp-monorepo-react-nestjs/apps/server/src/common/model/response.ts +0 -23
  279. package/template-vp-monorepo-react-nestjs/apps/server/src/config/auth.config.ts +0 -64
  280. package/template-vp-monorepo-react-nestjs/apps/web-vue/pnpm-workspace.yaml +0 -16
  281. /package/{template-vp-monorepo-react-nestjs/apps/server/src/config/security.config.ts → template-vp-monorepo-react-hono/.agents/auth.config.ts} +0 -0
@@ -0,0 +1,384 @@
1
+ import {
2
+ ArgumentsHost,
3
+ Catch,
4
+ ExceptionFilter,
5
+ HttpException,
6
+ HttpStatus,
7
+ Logger,
8
+ } from '@nestjs/common'
9
+ import { HttpAdapterHost } from '@nestjs/core'
10
+ import { FastifyReply, FastifyRequest } from 'fastify'
11
+ import { ZodSerializationException, ZodValidationException } from 'nestjs-zod'
12
+ import { ApiErrorDetail, ApiFailure } from 'shared/types'
13
+ import { ZodError } from 'zod'
14
+ import { COMMON_ERRORS } from '../error/common.error'
15
+ import { AppException } from '../exceptions/app.exception'
16
+
17
+ interface PublicHttpError {
18
+ readonly code: string
19
+ readonly message: string
20
+ }
21
+
22
+ const MAX_DETAIL_COUNT = 20
23
+ const MAX_DETAIL_PATH_LENGTH = 200
24
+ /**
25
+ * 错误响应体的 details[number].code 的最长长度限制
26
+ */
27
+ const MAX_DETAIL_CODE_LENGTH = 80
28
+ const MAX_PUBLIC_MESSAGE_LENGTH = 300
29
+ const MAX_LOG_MESSAGE_LENGTH = 1000
30
+ const MAX_LOG_FRAME_LENGTH = 600
31
+ const MAX_LOG_STACK_FRAMES = 12
32
+
33
+ interface NormalizedFailure {
34
+ readonly status: number
35
+ readonly body: ApiFailure
36
+ readonly cause?: unknown
37
+ readonly bearerChallenge?: boolean
38
+ }
39
+
40
+ const PUBLIC_HTTP_ERRORS: Readonly<Record<number, PublicHttpError>> = {
41
+ [HttpStatus.BAD_REQUEST]: {
42
+ code: 'COMMON.BAD_REQUEST',
43
+ message: '请求内容无效',
44
+ },
45
+ [HttpStatus.UNAUTHORIZED]: {
46
+ code: 'COMMON.UNAUTHORIZED',
47
+ message: '身份认证失败',
48
+ },
49
+ [HttpStatus.FORBIDDEN]: {
50
+ code: 'COMMON.FORBIDDEN',
51
+ message: '没有权限执行此操作',
52
+ },
53
+ [HttpStatus.NOT_FOUND]: {
54
+ code: 'COMMON.NOT_FOUND',
55
+ message: '请求的资源不存在',
56
+ },
57
+ [HttpStatus.METHOD_NOT_ALLOWED]: {
58
+ code: 'COMMON.METHOD_NOT_ALLOWED',
59
+ message: '请求方法不受支持',
60
+ },
61
+ [HttpStatus.CONFLICT]: {
62
+ code: 'COMMON.CONFLICT',
63
+ message: '当前资源状态存在冲突',
64
+ },
65
+ [HttpStatus.PAYLOAD_TOO_LARGE]: {
66
+ code: 'COMMON.PAYLOAD_TOO_LARGE',
67
+ message: '请求内容过大',
68
+ },
69
+ [HttpStatus.UNSUPPORTED_MEDIA_TYPE]: {
70
+ code: 'COMMON.UNSUPPORTED_MEDIA_TYPE',
71
+ message: '请求内容类型不受支持',
72
+ },
73
+ [HttpStatus.UNPROCESSABLE_ENTITY]: {
74
+ code: 'COMMON.UNPROCESSABLE_CONTENT',
75
+ message: '请求内容无法处理',
76
+ },
77
+ [HttpStatus.TOO_MANY_REQUESTS]: {
78
+ code: 'COMMON.TOO_MANY_REQUESTS',
79
+ message: '请求过于频繁,请稍后重试',
80
+ },
81
+ }
82
+
83
+ function cleanPublicText(value: unknown, maxLength: number): string {
84
+ if (typeof value !== 'string') {
85
+ return ''
86
+ }
87
+
88
+ // 这段代码是在把字符串中的“不可见控制字符和换行符”全部替换成空格:
89
+ // \u0000-\u001F:ASCII 控制字符,包括换行 \n、回车 \r、Tab 等。
90
+ // \u007F-\u009F:其他不可见控制字符。
91
+ // \u2028:Unicode 行分隔符。
92
+ // \u2029:Unicode 段落分隔符。
93
+ return (
94
+ value
95
+ // oxlint-disable-next-line no-control-regex
96
+ .replace(/[\u0000-\u001F\u007F-\u009F\u2028\u2029]/g, ' ')
97
+ .trim()
98
+ .slice(0, maxLength)
99
+ )
100
+ }
101
+
102
+ function cleanDetailCode(value: unknown): string {
103
+ const code = cleanPublicText(value, MAX_DETAIL_CODE_LENGTH)
104
+ if (/^[A-Za-z][A-Za-z0-9._-]*$/.test(code)) {
105
+ return code
106
+ }
107
+ return 'invalid'
108
+ }
109
+
110
+ function normalizeDetails(
111
+ details: readonly ApiErrorDetail[] | undefined,
112
+ ): ApiErrorDetail[] | undefined {
113
+ const normalized = details?.slice(0, MAX_DETAIL_COUNT).map((detail) => {
114
+ const path = cleanPublicText(detail.path, MAX_DETAIL_PATH_LENGTH)
115
+ const message = cleanPublicText(detail.message, MAX_PUBLIC_MESSAGE_LENGTH) || '请求内容无效'
116
+
117
+ return {
118
+ ...(path ? { path } : {}),
119
+ code: cleanDetailCode(detail.code),
120
+ message,
121
+ }
122
+ })
123
+ return normalized?.length ? normalized : undefined
124
+ }
125
+
126
+ function normalizeStatus(value: unknown): number {
127
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
128
+ return HttpStatus.INTERNAL_SERVER_ERROR
129
+ }
130
+ if (value < 400 || value > 599) {
131
+ return HttpStatus.INTERNAL_SERVER_ERROR
132
+ }
133
+
134
+ return value
135
+ }
136
+
137
+ /**
138
+ * 对日志文本进行脱敏处理
139
+ * @param value 日志文本
140
+ * @param maxLength 最大长度
141
+ * @returns 脱敏后的日志文本
142
+ */
143
+ function sanitizeLogText(value: string, maxLength: number): string {
144
+ return value
145
+ .replace(/[\u0000-\u001F\u007F\u2028\u2029]/g, ' ')
146
+ .replace(/\b([a-z][a-z0-9+.-]*:\/\/)([^@\s/]+)@/gi, '$1[REDACTED]@')
147
+ .replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, '$1 [REDACTED]')
148
+ .replace(
149
+ /([?&](?:password|passwd|pwd|token|access[_-]?token|refresh[_-]?token|api[_-]?key|secret|client[_-]?secret)=)[^&#\s]*/gi,
150
+ '$1[REDACTED]',
151
+ )
152
+ .replace(
153
+ /(["']?)(authorization|proxy-authorization|cookie|set-cookie)\1\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^,}\]]+)/gi,
154
+ '$1$2$1=[REDACTED]',
155
+ )
156
+ .replace(
157
+ /(["']?)(password|passwd|pwd|token|access[_-]?token|refresh[_-]?token|api[_-]?key|x-api-key|secret|client[_-]?secret|database[_-]?url|redis[_-]?url|connection[_-]?string|dsn)\1\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;}\]]+)/gi,
158
+ '$1$2$1=[REDACTED]',
159
+ )
160
+ .replace(/\s+/g, ' ')
161
+ .trim()
162
+ .slice(0, maxLength)
163
+ }
164
+ /**
165
+ * /u2028 行分隔符
166
+ * /u2029 段落分隔符
167
+ * @param error
168
+ * @returns
169
+ */
170
+ function sanitizeStackFrame(error: Error): string[] | undefined {
171
+ if (!error.stack || /[\r\n\u2028\u2029]/.test(error.message)) {
172
+ return undefined
173
+ }
174
+
175
+ const frames = error.stack
176
+ .split(/\r?\n/)
177
+ .slice(1)
178
+ .map((line) => line.trim())
179
+ .filter((line) => line.startsWith('at '))
180
+ .slice(0, MAX_LOG_STACK_FRAMES)
181
+ .map((line) => sanitizeLogText(line, MAX_LOG_FRAME_LENGTH))
182
+ .filter(Boolean)
183
+ return frames.length ? frames : undefined
184
+ }
185
+
186
+ function serializeError(value: unknown, depth = 0): Record<string, unknown> {
187
+ if (!(value instanceof Error)) {
188
+ return {
189
+ type: typeof value,
190
+ }
191
+ }
192
+
193
+ const stackFrames = sanitizeStackFrame(value)
194
+ const result: Record<string, unknown> = {
195
+ name: sanitizeLogText(value.name, 120) || 'Error',
196
+ message: sanitizeLogText(value.message, MAX_LOG_MESSAGE_LENGTH),
197
+ ...(stackFrames ? { stackFrames } : {}),
198
+ }
199
+ if (depth < 2 && value.cause !== undefined) {
200
+ result.cause = serializeError(value.cause, depth + 1)
201
+ }
202
+ return result
203
+ }
204
+
205
+ @Catch()
206
+ export class GlobalExceptionFilter implements ExceptionFilter {
207
+ private readonly logger = new Logger(GlobalExceptionFilter.name)
208
+
209
+ constructor(private readonly adapterHost: HttpAdapterHost) {}
210
+
211
+ private getZodDetails(exception: ZodValidationException): ApiErrorDetail[] | undefined {
212
+ const zodError = exception.getZodError()
213
+
214
+ if (!(zodError instanceof ZodError)) {
215
+ return undefined
216
+ }
217
+
218
+ const details = zodError.issues.slice(0, MAX_DETAIL_COUNT).map((issue) => {
219
+ const path = cleanPublicText(issue.path.map(String).join('.'), MAX_DETAIL_PATH_LENGTH)
220
+ const message = cleanPublicText(issue.message, MAX_PUBLIC_MESSAGE_LENGTH) || '请求内容无效'
221
+
222
+ return {
223
+ ...(path ? { path } : {}),
224
+ code: cleanDetailCode(issue.code),
225
+ message,
226
+ }
227
+ })
228
+ return details.length ? details : undefined
229
+ }
230
+
231
+ private normalizeLegacyHttpException(exception: HttpException): NormalizedFailure {
232
+ const status = normalizeStatus(exception.getStatus())
233
+
234
+ if (status >= 500) {
235
+ const serviceUnavailabel =
236
+ status === HttpStatus.BAD_GATEWAY ||
237
+ status === HttpStatus.SERVICE_UNAVAILABLE ||
238
+ status === HttpStatus.GATEWAY_TIMEOUT
239
+
240
+ return {
241
+ status,
242
+ body: serviceUnavailabel
243
+ ? {
244
+ code: COMMON_ERRORS.SERVICE_UNAVAILABLE.code,
245
+ message: COMMON_ERRORS.SERVICE_UNAVAILABLE.publicMessage,
246
+ }
247
+ : {
248
+ code: COMMON_ERRORS.INTERNAL_SERVER_ERROR.code,
249
+ message: COMMON_ERRORS.INTERNAL_SERVER_ERROR.publicMessage,
250
+ },
251
+ cause: exception,
252
+ }
253
+ }
254
+
255
+ const publicError = PUBLIC_HTTP_ERRORS[status] ?? {
256
+ code: 'COMMON.HTTP_' + status,
257
+ message: '请求处理失败',
258
+ }
259
+ return {
260
+ status,
261
+ body: publicError,
262
+ bearerChallenge: status === HttpStatus.UNAUTHORIZED,
263
+ }
264
+ }
265
+
266
+ private normalize(exception: unknown): NormalizedFailure {
267
+ if (exception instanceof ZodSerializationException) {
268
+ return {
269
+ status: HttpStatus.INTERNAL_SERVER_ERROR,
270
+ body: {
271
+ code: COMMON_ERRORS.INTERNAL_SERVER_ERROR.code,
272
+ message: COMMON_ERRORS.INTERNAL_SERVER_ERROR.publicMessage,
273
+ },
274
+ cause: exception.getZodError() ?? exception,
275
+ }
276
+ }
277
+
278
+ if (exception instanceof ZodValidationException) {
279
+ const details = this.getZodDetails(exception)
280
+
281
+ return {
282
+ status: COMMON_ERRORS.VALIDATION_FAILED.status,
283
+ body: {
284
+ code: COMMON_ERRORS.VALIDATION_FAILED.code,
285
+ message: COMMON_ERRORS.VALIDATION_FAILED.publicMessage,
286
+ ...(details?.length ? { details } : {}),
287
+ },
288
+ }
289
+ }
290
+
291
+ if (exception instanceof AppException) {
292
+ const status = normalizeStatus(exception.getStatus())
293
+ const details = normalizeDetails(exception.details)
294
+ const message =
295
+ cleanPublicText(exception.definition.publicMessage, MAX_PUBLIC_MESSAGE_LENGTH) ||
296
+ '请求处理失败'
297
+ return {
298
+ status,
299
+ body: {
300
+ code: exception.definition.code,
301
+ message,
302
+ ...(details?.length ? { details } : {}),
303
+ },
304
+ cause: status >= 500 ? (exception.cause ?? exception) : undefined,
305
+ bearerChallenge: exception.definition.bearerChallenge,
306
+ }
307
+ }
308
+
309
+ if (exception instanceof HttpException) {
310
+ return this.normalizeLegacyHttpException(exception)
311
+ }
312
+
313
+ return {
314
+ status: HttpStatus.INTERNAL_SERVER_ERROR,
315
+ body: {
316
+ code: COMMON_ERRORS.INTERNAL_SERVER_ERROR.code,
317
+ message: COMMON_ERRORS.INTERNAL_SERVER_ERROR.publicMessage,
318
+ },
319
+ cause: exception,
320
+ }
321
+ }
322
+
323
+ private logServerFailure(
324
+ exception: unknown,
325
+ normalized: NormalizedFailure,
326
+ request: FastifyRequest,
327
+ requestId: string,
328
+ ): void {
329
+ const route = request.routeOptions?.url ?? 'unmatched'
330
+ const cause = normalized.cause ?? exception
331
+
332
+ this.logger.error({
333
+ event: 'http_request_failed',
334
+ requestId,
335
+ method: request.method,
336
+ route,
337
+ status: normalized.status,
338
+ code: normalized.body.code,
339
+ error: serializeError(cause),
340
+ })
341
+ }
342
+
343
+ catch(exception: unknown, host: ArgumentsHost): void {
344
+ const http = host.switchToHttp()
345
+ const request = http.getRequest<FastifyRequest>()
346
+ const response = http.getResponse<FastifyReply>()
347
+ const requestId = String(request.id)
348
+ const normalized = this.normalize(exception)
349
+ const { httpAdapter } = this.adapterHost
350
+
351
+ // 只记录服务器错误 业务错误不记录
352
+ if (normalized.status >= 500) {
353
+ this.logServerFailure(exception, normalized, request, requestId)
354
+ }
355
+
356
+ // 如果响应头已经被发送了 则直接结束响应 不再进行后面发的处理
357
+ if (httpAdapter.isHeadersSent(response)) {
358
+ httpAdapter.end(response)
359
+ return
360
+ }
361
+
362
+ httpAdapter.setHeader(response, 'x-request-id', requestId)
363
+
364
+ /** 如果是未授权错误 则添加WWW-Authenticate 头 相当于告诉客户 请使用Bearer 认证
365
+ * 为什么使用 normalized.bearerChallenge 而不是 normalized.status === HttpStatus.UNAUTHORIZED
366
+ * 因为 normalized.status === HttpStatus.UNAUTHORIZED 只是告诉客户 授权有问题
367
+ * 以下情况 bearerChallenge 为 false
368
+ * 账号被禁用/锁定、权限不足、API Key 认证失败 、Basic Auth 失败、IP / 网络层拦截、Session/Cookie 失效
369
+ * 为true 的场景
370
+ * 应该设为 true 的场景
371
+ 异常类型 原因 为什么需要 Bearer 质询
372
+ Token 缺失 请求头没有 Authorization 客户端需要知道“这里要传 Bearer Token”
373
+ Token 格式错误 不是合法的 JWT / 签名不对 客户端可能传错了 Token 类型或损坏了
374
+ Token 过期 exp 时间已过 客户端需要用 refresh_token 换取新 Token
375
+ Token 签发者/受众不匹配 iss / aud 校验失败 客户端用了错误环境的 Token
376
+ Token 被吊销 在黑名单 / 撤销列表中 客户端需要重新获取 Token
377
+ */
378
+ if (normalized.bearerChallenge) {
379
+ httpAdapter.setHeader(response, 'www-authenticate', 'Bearer realm="api"')
380
+ }
381
+
382
+ httpAdapter.reply(response, normalized.body, normalized.status)
383
+ }
384
+ }
@@ -0,0 +1,9 @@
1
+ export const REQUEST_ID_RESPONSE_HEADERS = {
2
+ 'X-Request-Id': {
3
+ description: '服务端生成的请求关联 ID',
4
+ schema: {
5
+ type: 'string',
6
+ format: 'uuid',
7
+ },
8
+ },
9
+ }
@@ -2,4 +2,4 @@ export { default as appConfig } from './app.config'
2
2
  export { default as databaseConfig } from './database.config'
3
3
  export { default as llmConfig } from './llm.config'
4
4
  export { default as redisConfig } from './redis.config'
5
- export { default as authConfig } from './auth.config'
5
+ export { default as sessionConfig } from './session.config'
@@ -0,0 +1,36 @@
1
+ import { registerAs } from '@nestjs/config'
2
+
3
+ function readPositiveInteger(name: string, fallback: number) {
4
+ const value = Number.parseInt(process.env[name] ?? String(fallback), 10)
5
+
6
+ if (!Number.isSafeInteger(value) || value <= 0) {
7
+ throw new Error(`${name} must be a positive integer`)
8
+ }
9
+ return value
10
+ }
11
+
12
+ export default registerAs('session', () => {
13
+ const tokenPepper = process.env.SESSION_TOKEN_PEPPER
14
+ /**
15
+ * 没有置换的情况下的过期时间 2个小时
16
+ */
17
+ const idleTtlSeconds = readPositiveInteger('SESSION_IDLE_TTL_SECONDS', 7200)
18
+ /**
19
+ * 绝对过期时间7天
20
+ */
21
+ const absoluteTtlSeconds = readPositiveInteger('SESSION_ABSOLUTE_TTL_SECONDS', 604800)
22
+
23
+ if (!tokenPepper || tokenPepper.length < 32) {
24
+ throw new Error('SESSION_TOKEN_PEPPER must contain at least 32 characters')
25
+ }
26
+
27
+ if (absoluteTtlSeconds <= idleTtlSeconds) {
28
+ throw new Error('SESSION_ABSOLUTE_TTL_SECONDS must be greater than idle TTL')
29
+ }
30
+
31
+ return {
32
+ tokenPepper,
33
+ idleTtlMs: +idleTtlSeconds * 1000,
34
+ absoluteTtlMs: +absoluteTtlSeconds * 1000,
35
+ }
36
+ })
@@ -1,14 +1,13 @@
1
- import { pgEnum } from 'drizzle-orm/pg-core'
2
- import { pgTable, serial, varchar, timestamp } from 'drizzle-orm/pg-core'
1
+ import { pgEnum, pgTable, timestamp, uuid, varchar } from 'drizzle-orm/pg-core'
3
2
 
4
- export const useStatusEnum = pgEnum('user_status', ['active', 'locked', 'disabled'])
5
-
6
- export const authSessionStatusEnum = pgEnum('auth_session_status', ['active', 'revoked'])
3
+ export const userStatusEnum = pgEnum('user_status', ['active', 'locked', 'disabled'])
7
4
 
8
5
  export const users = pgTable('users', {
9
- id: serial('id').primaryKey(),
6
+ id: uuid('id').defaultRandom().primaryKey(),
10
7
  name: varchar('name', { length: 100 }).notNull(),
11
- email: varchar('email', { length: 255 }).notNull().unique(),
12
- password: varchar('password', { length: 255 }).notNull(),
8
+ account: varchar('account', { length: 32 }).notNull().unique(),
9
+ passwordHash: varchar('password_hash', { length: 255 }).notNull(),
10
+ status: userStatusEnum('status').default('active').notNull(),
13
11
  createdAt: timestamp('create_at').defaultNow().notNull(),
12
+ updatedAt: timestamp('update_at', { withTimezone: true }).defaultNow().notNull(),
14
13
  })
@@ -1,21 +1,38 @@
1
- import { NestFactory } from '@nestjs/core'
2
1
  import { AppModule } from '@/app.module'
3
2
  import { logNetworkUrls } from '@/utils/server-address'
4
- import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify'
3
+ import { ConsoleLogger, Logger } from '@nestjs/common'
4
+ import { NestFactory } from '@nestjs/core'
5
+ import { type NestFastifyApplication } from '@nestjs/platform-fastify'
5
6
  import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
6
7
  import { cleanupOpenApiDoc } from 'nestjs-zod'
7
- import fastifyCookie from '@fastify/cookie'
8
+ import { createFastifyAdapter } from './common/adapters/fastify.adapter'
8
9
 
9
10
  async function bootstrap() {
10
- const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter())
11
-
12
- await app.register(fastifyCookie)
11
+ const app = await NestFactory.create<NestFastifyApplication>(AppModule, createFastifyAdapter(), {
12
+ logger: new ConsoleLogger({
13
+ json: process.env.NODE_ENV === 'production',
14
+ colors: process.env.NODE_ENV === 'development',
15
+ }),
16
+ })
17
+ app.enableCors({
18
+ origin: ['*'],
19
+ allowedHeaders: ['Content-Type', 'Authorization', 'X-Company-Id'],
20
+ methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
21
+ exposedHeaders: ['X-Request-Id', 'WWW-Authenticate', 'Retry-After'],
22
+ })
13
23
 
14
24
  const config = new DocumentBuilder()
15
25
  .setTitle('前后端模板 API')
16
26
  .setDescription('接口文档')
17
27
  .setVersion('1.0')
18
- .addBearerAuth() // 用 JWT 就留着,不用可以删
28
+ .addBearerAuth(
29
+ {
30
+ type: 'http',
31
+ scheme: 'bearer',
32
+ bearerFormat: 'Opaque Session Token',
33
+ },
34
+ 'session',
35
+ ) // 用 JWT 就留着,不用可以删
19
36
  .build()
20
37
 
21
38
  // 生成openAPI 文档
@@ -31,4 +48,8 @@ async function bootstrap() {
31
48
  logNetworkUrls(port)
32
49
  }
33
50
 
34
- void bootstrap()
51
+ void bootstrap().catch((cause: unknown) => {
52
+ const logger = new Logger('Bootstrap')
53
+ logger.error('Server boostrap failed', cause instanceof Error ? cause.stack : undefined)
54
+ process.exitCode = 1
55
+ })
@@ -0,0 +1,55 @@
1
+ import { CurrentAuth } from '@/common/decorators/current-auth.decorator'
2
+ import { Public } from '@/common/decorators/public.decorator'
3
+ import { Body, Controller, Get, Headers, HttpCode, HttpStatus, Post, Req } from '@nestjs/common'
4
+ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'
5
+ import type { FastifyRequest } from 'fastify'
6
+ import { CurrentUser } from 'shared/types'
7
+ import { AuthService } from './auth.service'
8
+ import { LoginDto } from './dto/login.dto'
9
+ import { RegisterDto } from './dto/register.dto'
10
+ import type { CurrentAuthType } from './session/session.types'
11
+
12
+ @ApiTags('认证')
13
+ @Controller('auth')
14
+ export class AuthController {
15
+ constructor(private readonly authService: AuthService) {}
16
+
17
+ @Public()
18
+ @ApiOperation({ summary: '注册' })
19
+ @Post('register')
20
+ register(@Body() body: RegisterDto) {
21
+ return this.authService.register(body)
22
+ }
23
+
24
+ @Public()
25
+ @ApiOperation({ summary: '登录并创建 Redis Session' })
26
+ @HttpCode(HttpStatus.OK)
27
+ @Post('login')
28
+ login(@Body() body: LoginDto, @Req() request: FastifyRequest) {
29
+ return this.authService.login(body, {
30
+ ip: request.ip,
31
+ userAgent: request.headers['user-agent'] ?? '',
32
+ })
33
+ }
34
+
35
+ @ApiBearerAuth('session')
36
+ @ApiOperation({ summary: '退出当前端' })
37
+ @Public()
38
+ @HttpCode(HttpStatus.OK)
39
+ @Post('logout')
40
+ logout(@Headers('authorization') authorization: string | undefined) {
41
+ return this.authService.logout(authorization)
42
+ }
43
+
44
+ @ApiBearerAuth('session')
45
+ @ApiOperation({ summary: '使用 Session Token 获取当前用户资料' })
46
+ @Get('me')
47
+ async me(@CurrentAuth() auth: CurrentAuthType): Promise<CurrentUser> {
48
+ const user = await this.authService.getCurrentUser(auth.userId)
49
+
50
+ return {
51
+ ...user,
52
+ terminal: auth.terminal,
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,42 @@
1
+ import type { AppErrorDefinition } from '@/common/exceptions/app.exception'
2
+ import { HttpStatus } from '@nestjs/common'
3
+
4
+ export const AUTH_ERRORS = {
5
+ SESSION_TOKEN_MISSING: {
6
+ code: 'AUTH.SESSION_TOKEN_MISSING',
7
+ publicMessage: '缺少有效的登录 Token',
8
+ status: HttpStatus.UNAUTHORIZED,
9
+ bearerChallenge: true,
10
+ },
11
+ SESSION_INVALID: {
12
+ code: 'AUTH.SESSION_INVALID',
13
+ publicMessage: '登录已失效,请重新登录',
14
+ status: HttpStatus.UNAUTHORIZED,
15
+ bearerChallenge: true,
16
+ },
17
+ CONTEXT_MISSING: {
18
+ code: 'AUTH.CONTEXT_MISSING',
19
+ publicMessage: '服务器内部错误',
20
+ status: HttpStatus.INTERNAL_SERVER_ERROR,
21
+ },
22
+ INVALID_CREDENTIALS: {
23
+ code: 'AUTH.INVALID_CREDENTIALS',
24
+ publicMessage: '账号或密码错误',
25
+ status: HttpStatus.UNPROCESSABLE_ENTITY,
26
+ },
27
+ ACCOUNT_ALREADY_EXISTS: {
28
+ code: 'AUTH.ACCOUNT_ALREADY_EXISTS',
29
+ publicMessage: '账号已存在',
30
+ status: HttpStatus.CONFLICT,
31
+ },
32
+ FORBIDDEN: {
33
+ code: 'AUTH.FORBIDDEN',
34
+ publicMessage: '没有权限执行此操作',
35
+ status: HttpStatus.FORBIDDEN,
36
+ },
37
+ SERVICE_UNAVAILABLE: {
38
+ code: 'AUTH.SERVICE_UNAVAILABLE',
39
+ publicMessage: '登录服务暂时不可用,请稍后重试',
40
+ status: HttpStatus.SERVICE_UNAVAILABLE,
41
+ },
42
+ } as const satisfies Record<string, AppErrorDefinition>
@@ -0,0 +1,26 @@
1
+ import { Module } from '@nestjs/common'
2
+ import { APP_GUARD } from '@nestjs/core'
3
+ import { AuthController } from './auth.controller'
4
+ import { AuthRepository } from './auth.repository'
5
+ import { AuthService } from './auth.service'
6
+ import { SessionAuthGuard } from './guards/session-auth.guard'
7
+ import { PasswordService } from './password.service'
8
+ import { SessionStoreService } from './session/session-store.service'
9
+ import { SessionTokenService } from './session/session-token.service'
10
+
11
+ @Module({
12
+ controllers: [AuthController],
13
+ providers: [
14
+ AuthRepository,
15
+ AuthService,
16
+ PasswordService,
17
+ SessionTokenService,
18
+ SessionStoreService,
19
+ {
20
+ provide: APP_GUARD,
21
+ useClass: SessionAuthGuard,
22
+ },
23
+ ],
24
+ exports: [SessionStoreService],
25
+ })
26
+ export class AuthModule {}
@@ -0,0 +1,40 @@
1
+ import { DRIZZLE, type DrizzleDB } from '@/database/db.module'
2
+ import { users } from '@/database/schema'
3
+ import { Inject, Injectable } from '@nestjs/common'
4
+ import { eq } from 'drizzle-orm'
5
+
6
+ type CreateUserInput = Pick<typeof users.$inferInsert, 'name' | 'account' | 'passwordHash'>
7
+
8
+ @Injectable()
9
+ export class AuthRepository {
10
+ constructor(@Inject(DRIZZLE) private readonly db: DrizzleDB) {}
11
+
12
+ async findByAccount(account: string) {
13
+ const [user] = await this.db.select().from(users).where(eq(users.account, account)).limit(1)
14
+ return user ?? null
15
+ }
16
+
17
+ async findPublicById(userId: string) {
18
+ const [user] = await this.db
19
+ .select({
20
+ id: users.id,
21
+ account: users.account,
22
+ name: users.name,
23
+ status: users.status,
24
+ })
25
+ .from(users)
26
+ .where(eq(users.id, userId))
27
+ .limit(1)
28
+ return user ?? null
29
+ }
30
+
31
+ async createUser(input: CreateUserInput) {
32
+ const [user] = await this.db
33
+ .insert(users)
34
+ .values(input)
35
+ // 处理账号重复情况 ,不插入重复账号
36
+ .onConflictDoNothing({ target: users.account })
37
+ .returning()
38
+ return user ?? null
39
+ }
40
+ }