create-zhx-monorepo 0.2.2 → 2.0.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 (349) hide show
  1. package/README.md +1 -1
  2. package/bin/index.js +0 -0
  3. package/package.json +3 -3
  4. package/templates/monorepo-starter/apps/dashboard/components.json +20 -0
  5. package/templates/monorepo-starter/apps/dashboard/eslint.config.js +1 -0
  6. package/templates/monorepo-starter/apps/dashboard/next-env.d.ts +6 -0
  7. package/templates/monorepo-starter/apps/dashboard/next.config.mjs +30 -0
  8. package/templates/monorepo-starter/apps/dashboard/package.json +43 -0
  9. package/templates/monorepo-starter/apps/dashboard/postcss.config.mjs +1 -0
  10. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/admin/users/[id]/edit/page.tsx +8 -0
  11. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/admin/users/[id]/page.tsx +104 -0
  12. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/admin/users/new/page.tsx +7 -0
  13. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/admin/users/page.tsx +88 -0
  14. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/dashboard/data.json +614 -0
  15. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/dashboard/page.tsx +21 -0
  16. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/layout.tsx +40 -0
  17. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/media/page.tsx +7 -0
  18. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/users/account/page.tsx +75 -0
  19. package/templates/monorepo-starter/apps/dashboard/src/app/auth/[type]/page.tsx +19 -0
  20. package/templates/monorepo-starter/apps/dashboard/src/app/auth/layout.tsx +11 -0
  21. package/templates/monorepo-starter/apps/dashboard/src/app/auth/verify/page.tsx +11 -0
  22. package/templates/monorepo-starter/apps/dashboard/src/app/favicon.ico +0 -0
  23. package/templates/monorepo-starter/apps/dashboard/src/app/layout.tsx +28 -0
  24. package/templates/monorepo-starter/apps/dashboard/src/components/forms/AuthForm.tsx +345 -0
  25. package/templates/monorepo-starter/apps/dashboard/src/components/forms/CUUserForm.tsx +223 -0
  26. package/templates/monorepo-starter/apps/dashboard/src/components/forms/MediaForm.tsx +35 -0
  27. package/templates/monorepo-starter/apps/dashboard/src/components/forms/SocialAuthField.tsx +56 -0
  28. package/templates/monorepo-starter/apps/dashboard/src/components/layout/AppSidebar.tsx +141 -0
  29. package/templates/monorepo-starter/apps/dashboard/src/components/layout/DashboardChart.tsx +293 -0
  30. package/templates/monorepo-starter/apps/dashboard/src/components/layout/DashboardStats.tsx +104 -0
  31. package/templates/monorepo-starter/apps/dashboard/src/components/layout/Header.tsx +21 -0
  32. package/templates/monorepo-starter/apps/dashboard/src/components/layout/NavMain.tsx +99 -0
  33. package/templates/monorepo-starter/apps/dashboard/src/components/media/LibraryPopup.tsx +25 -0
  34. package/templates/monorepo-starter/apps/dashboard/src/components/media/MediaCard.tsx +175 -0
  35. package/templates/monorepo-starter/apps/dashboard/src/components/media/MediaDetails.tsx +58 -0
  36. package/templates/monorepo-starter/apps/dashboard/src/components/media/MediaGrid.tsx +51 -0
  37. package/templates/monorepo-starter/apps/dashboard/src/components/media/MediaUploader.tsx +182 -0
  38. package/templates/monorepo-starter/apps/dashboard/src/components/media/index.tsx +96 -0
  39. package/templates/monorepo-starter/apps/dashboard/src/components/shared/DateWrapper.tsx +43 -0
  40. package/templates/monorepo-starter/apps/dashboard/src/components/shared/GenericArrayField.tsx +189 -0
  41. package/templates/monorepo-starter/apps/dashboard/src/components/shared/GenericCUForm.tsx +124 -0
  42. package/templates/monorepo-starter/apps/dashboard/src/components/shared/GenericDetailsPage.tsx +291 -0
  43. package/templates/monorepo-starter/apps/dashboard/src/components/shared/GenericFormTable.tsx +189 -0
  44. package/templates/monorepo-starter/apps/dashboard/src/components/shared/GenericTable.tsx +244 -0
  45. package/templates/monorepo-starter/apps/dashboard/src/components/shared/ListPage.tsx +134 -0
  46. package/templates/monorepo-starter/apps/dashboard/src/components/shared/Pagination.tsx +120 -0
  47. package/templates/monorepo-starter/apps/dashboard/src/components/shared/SearchToolbar.tsx +133 -0
  48. package/templates/monorepo-starter/apps/dashboard/src/components/shared/ThemeSwitch.tsx +41 -0
  49. package/templates/monorepo-starter/apps/dashboard/src/components/shared/VerifyAuthPage.tsx +109 -0
  50. package/templates/monorepo-starter/apps/dashboard/src/components/skeleton/CUFormSkeleton.tsx +31 -0
  51. package/templates/monorepo-starter/apps/dashboard/src/components/skeleton/DashboardSke1eton.tsx +135 -0
  52. package/templates/monorepo-starter/apps/dashboard/src/components/skeleton/DetailsPageSkeleton.tsx +64 -0
  53. package/templates/monorepo-starter/apps/dashboard/src/components/skeleton/RootLayoutSkeleton.tsx +106 -0
  54. package/templates/monorepo-starter/apps/dashboard/src/components/skeleton/TableSkeleton.tsx +77 -0
  55. package/templates/monorepo-starter/apps/dashboard/src/components/user/UserAccountSection.tsx +530 -0
  56. package/templates/monorepo-starter/apps/dashboard/src/components/user/UserCard.tsx +54 -0
  57. package/templates/monorepo-starter/apps/dashboard/src/components/user/UserProfileSection.tsx +239 -0
  58. package/templates/monorepo-starter/apps/dashboard/src/components/user/UserSessions.tsx +154 -0
  59. package/templates/monorepo-starter/apps/dashboard/src/hooks/admin.ts +100 -0
  60. package/templates/monorepo-starter/apps/dashboard/src/hooks/auth.ts +59 -0
  61. package/templates/monorepo-starter/apps/dashboard/src/hooks/crud.ts +275 -0
  62. package/templates/monorepo-starter/apps/dashboard/src/hooks/lookup.ts +38 -0
  63. package/templates/monorepo-starter/apps/dashboard/src/hooks/media.ts +34 -0
  64. package/templates/monorepo-starter/apps/dashboard/src/hooks/user.ts +58 -0
  65. package/templates/monorepo-starter/apps/dashboard/src/lib/constants.ts +61 -0
  66. package/templates/monorepo-starter/apps/dashboard/src/providers/index.tsx +15 -0
  67. package/templates/monorepo-starter/apps/dashboard/src/providers/media-library.tsx +48 -0
  68. package/templates/monorepo-starter/apps/dashboard/tsconfig.json +20 -0
  69. package/templates/monorepo-starter/apps/web/components.json +20 -20
  70. package/templates/monorepo-starter/apps/web/eslint.config.js +1 -0
  71. package/templates/monorepo-starter/apps/web/next-env.d.ts +6 -6
  72. package/templates/monorepo-starter/apps/web/next.config.mjs +20 -6
  73. package/templates/monorepo-starter/apps/web/package.json +36 -31
  74. package/templates/monorepo-starter/apps/web/postcss.config.mjs +1 -1
  75. package/templates/monorepo-starter/apps/web/src/app/(root)/layout.tsx +2 -2
  76. package/templates/monorepo-starter/apps/web/src/app/favicon.ico +0 -0
  77. package/templates/monorepo-starter/apps/web/src/app/layout.tsx +13 -9
  78. package/templates/monorepo-starter/apps/web/src/components/Footer.tsx +5 -7
  79. package/templates/monorepo-starter/apps/web/src/components/Header.tsx +5 -9
  80. package/templates/monorepo-starter/apps/web/tsconfig.json +20 -20
  81. package/templates/monorepo-starter/eslint.config.mjs +1 -1
  82. package/templates/monorepo-starter/gitignore +13 -4
  83. package/templates/monorepo-starter/package.json +49 -23
  84. package/templates/monorepo-starter/packages/config/eslint/next.mjs +18 -0
  85. package/templates/monorepo-starter/packages/config/eslint/node.mjs +44 -0
  86. package/templates/monorepo-starter/packages/config/eslint/react.mjs +68 -0
  87. package/templates/monorepo-starter/packages/config/package.json +23 -32
  88. package/templates/monorepo-starter/packages/config/typescript/node.json +25 -0
  89. package/templates/monorepo-starter/packages/config/typescript/react.json +20 -3
  90. package/templates/monorepo-starter/packages/contracts/eslint.config.mjs +1 -0
  91. package/templates/monorepo-starter/packages/contracts/package.json +51 -0
  92. package/templates/monorepo-starter/packages/contracts/src/admin/dto.ts +6 -0
  93. package/templates/monorepo-starter/packages/contracts/src/admin/index.ts +2 -0
  94. package/templates/monorepo-starter/packages/contracts/src/admin/schema.ts +28 -0
  95. package/templates/monorepo-starter/packages/contracts/src/admin/types.d.ts +27 -0
  96. package/templates/monorepo-starter/packages/contracts/src/auth/dto.ts +24 -0
  97. package/templates/monorepo-starter/packages/contracts/src/auth/index.ts +2 -0
  98. package/templates/monorepo-starter/packages/contracts/src/auth/schema.ts +35 -0
  99. package/templates/monorepo-starter/packages/contracts/src/auth/types.d.ts +73 -0
  100. package/templates/monorepo-starter/packages/contracts/src/lib/enums.ts +55 -0
  101. package/templates/monorepo-starter/packages/contracts/src/lib/index.ts +4 -0
  102. package/templates/monorepo-starter/packages/contracts/src/lib/prisma.ts +2 -0
  103. package/templates/monorepo-starter/packages/contracts/src/lib/schema.ts +61 -0
  104. package/templates/monorepo-starter/packages/contracts/src/lib/types.d.ts +149 -0
  105. package/templates/monorepo-starter/packages/contracts/src/lib/utils.ts +8 -0
  106. package/templates/monorepo-starter/packages/contracts/src/media/dto.ts +12 -0
  107. package/templates/monorepo-starter/packages/contracts/src/media/index.ts +2 -0
  108. package/templates/monorepo-starter/packages/contracts/src/media/schema.ts +27 -0
  109. package/templates/monorepo-starter/packages/contracts/src/media/types.d.ts +36 -0
  110. package/templates/monorepo-starter/packages/contracts/src/notification/dto.ts +6 -0
  111. package/templates/monorepo-starter/packages/contracts/src/notification/index.ts +2 -0
  112. package/templates/monorepo-starter/packages/contracts/src/notification/schema.ts +7 -0
  113. package/templates/monorepo-starter/packages/contracts/src/notification/types.d.ts +22 -0
  114. package/templates/monorepo-starter/packages/contracts/src/user/dto.ts +4 -0
  115. package/templates/monorepo-starter/packages/contracts/src/user/index.ts +2 -0
  116. package/templates/monorepo-starter/packages/contracts/src/user/schema.ts +16 -0
  117. package/templates/monorepo-starter/packages/contracts/src/user/types.d.ts +36 -0
  118. package/templates/monorepo-starter/packages/contracts/tsconfig.json +12 -0
  119. package/templates/monorepo-starter/packages/contracts/tsup.config.ts +20 -0
  120. package/templates/monorepo-starter/packages/sdk/eslint.config.mjs +1 -0
  121. package/templates/monorepo-starter/packages/sdk/package.json +31 -0
  122. package/templates/monorepo-starter/packages/sdk/src/admin/index.ts +31 -0
  123. package/templates/monorepo-starter/packages/sdk/src/auth/index.ts +71 -0
  124. package/templates/monorepo-starter/packages/sdk/src/lib/api-client.ts +71 -0
  125. package/templates/monorepo-starter/packages/sdk/src/lib/index.ts +2 -0
  126. package/templates/monorepo-starter/packages/sdk/src/lib/types.ts +40 -0
  127. package/templates/monorepo-starter/packages/sdk/src/media/index.ts +27 -0
  128. package/templates/monorepo-starter/packages/sdk/src/notification/index.ts +10 -0
  129. package/templates/monorepo-starter/packages/sdk/src/public/index.ts +6 -0
  130. package/templates/monorepo-starter/packages/sdk/src/user/index.ts +7 -0
  131. package/templates/monorepo-starter/packages/sdk/tsconfig.json +12 -0
  132. package/templates/monorepo-starter/packages/shared/eslint.config.mjs +1 -0
  133. package/templates/monorepo-starter/packages/shared/package.json +36 -0
  134. package/templates/monorepo-starter/packages/shared/src/constants/app.ts +4 -0
  135. package/templates/monorepo-starter/packages/shared/src/constants/index.ts +1 -0
  136. package/templates/monorepo-starter/packages/shared/src/utils/date.ts +137 -0
  137. package/templates/monorepo-starter/packages/shared/src/utils/identifier.ts +11 -0
  138. package/templates/monorepo-starter/packages/shared/src/utils/index.ts +2 -0
  139. package/templates/monorepo-starter/packages/shared/tsconfig.json +12 -0
  140. package/templates/monorepo-starter/packages/shared/tsup.config.ts +16 -0
  141. package/templates/monorepo-starter/packages/templates/eslint.config.mjs +1 -0
  142. package/templates/monorepo-starter/packages/templates/package.json +38 -0
  143. package/templates/monorepo-starter/packages/templates/src/email/auth/disableMfa.tsx +40 -0
  144. package/templates/monorepo-starter/packages/templates/src/email/auth/index.ts +10 -0
  145. package/templates/monorepo-starter/packages/templates/src/email/auth/resetPassword.tsx +41 -0
  146. package/templates/monorepo-starter/packages/templates/src/email/auth/securityAlert.tsx +21 -0
  147. package/templates/monorepo-starter/packages/templates/src/email/auth/setPassword.tsx +42 -0
  148. package/templates/monorepo-starter/packages/templates/src/email/auth/signin.tsx +19 -0
  149. package/templates/monorepo-starter/packages/templates/src/email/auth/signup.tsx +24 -0
  150. package/templates/monorepo-starter/packages/templates/src/email/auth/updateIdentifier.tsx +79 -0
  151. package/templates/monorepo-starter/packages/templates/src/email/auth/updateMfa.tsx +40 -0
  152. package/templates/monorepo-starter/packages/templates/src/email/auth/verifyIdentifier.tsx +42 -0
  153. package/templates/monorepo-starter/packages/templates/src/email/auth/verifyMfa.tsx +23 -0
  154. package/templates/monorepo-starter/packages/templates/src/email/components/actionBlock.tsx +39 -0
  155. package/templates/monorepo-starter/packages/templates/src/email/components/footer.tsx +0 -0
  156. package/templates/monorepo-starter/packages/templates/src/email/components/greeting.tsx +11 -0
  157. package/templates/monorepo-starter/packages/templates/src/email/components/header.tsx +15 -0
  158. package/templates/monorepo-starter/packages/templates/src/email/components/layout.tsx +33 -0
  159. package/templates/monorepo-starter/packages/templates/src/email/components/numericCode.tsx +16 -0
  160. package/templates/monorepo-starter/packages/templates/src/email/index.ts +2 -0
  161. package/templates/monorepo-starter/packages/templates/src/email/service/accountReactivated.tsx +31 -0
  162. package/templates/monorepo-starter/packages/templates/src/email/service/accountSuspended.tsx +37 -0
  163. package/templates/monorepo-starter/packages/templates/src/email/service/index.ts +2 -0
  164. package/templates/monorepo-starter/packages/templates/src/email/test.tsx +141 -0
  165. package/templates/monorepo-starter/packages/templates/src/index.ts +1 -0
  166. package/templates/monorepo-starter/packages/templates/src/lib/constants.ts +22 -0
  167. package/templates/monorepo-starter/packages/templates/src/lib/utils.ts +19 -0
  168. package/templates/monorepo-starter/packages/templates/src/types/global.d.ts +37 -0
  169. package/templates/monorepo-starter/packages/templates/tsconfig.json +13 -0
  170. package/templates/monorepo-starter/packages/templates/tsup.config.ts +23 -0
  171. package/templates/monorepo-starter/packages/ui/components.json +1 -1
  172. package/templates/monorepo-starter/packages/ui/eslint.config.mjs +1 -4
  173. package/templates/monorepo-starter/packages/ui/package.json +81 -38
  174. package/templates/monorepo-starter/packages/ui/src/components/accordion.tsx +66 -0
  175. package/templates/monorepo-starter/packages/ui/src/components/airport-search-field.tsx +203 -0
  176. package/templates/monorepo-starter/packages/ui/src/components/alert-dialog.tsx +157 -0
  177. package/templates/monorepo-starter/packages/ui/src/components/alert.tsx +66 -0
  178. package/templates/monorepo-starter/packages/ui/src/components/aspect-ratio.tsx +11 -0
  179. package/templates/monorepo-starter/packages/ui/src/components/avatar.tsx +53 -0
  180. package/templates/monorepo-starter/packages/ui/src/components/badge.tsx +46 -0
  181. package/templates/monorepo-starter/packages/ui/src/components/breadcrumb.tsx +109 -0
  182. package/templates/monorepo-starter/packages/ui/src/components/button-group.tsx +83 -0
  183. package/templates/monorepo-starter/packages/ui/src/components/button.tsx +2 -1
  184. package/templates/monorepo-starter/packages/ui/src/components/calendar.tsx +220 -0
  185. package/templates/monorepo-starter/packages/ui/src/components/card.tsx +92 -0
  186. package/templates/monorepo-starter/packages/ui/src/components/carousel.tsx +241 -0
  187. package/templates/monorepo-starter/packages/ui/src/components/chart.tsx +357 -0
  188. package/templates/monorepo-starter/packages/ui/src/components/checkbox.tsx +32 -0
  189. package/templates/monorepo-starter/packages/ui/src/components/collapsible.tsx +33 -0
  190. package/templates/monorepo-starter/packages/ui/src/components/combobox-field.tsx +181 -0
  191. package/templates/monorepo-starter/packages/ui/src/components/combobox.tsx +309 -0
  192. package/templates/monorepo-starter/packages/ui/src/components/command.tsx +184 -0
  193. package/templates/monorepo-starter/packages/ui/src/components/context-menu.tsx +252 -0
  194. package/templates/monorepo-starter/packages/ui/src/components/date-field.tsx +65 -0
  195. package/templates/monorepo-starter/packages/ui/src/components/dialog.tsx +143 -0
  196. package/templates/monorepo-starter/packages/ui/src/components/direction.tsx +22 -0
  197. package/templates/monorepo-starter/packages/ui/src/components/drawer.tsx +135 -0
  198. package/templates/monorepo-starter/packages/ui/src/components/dropdown-menu.tsx +257 -0
  199. package/templates/monorepo-starter/packages/ui/src/components/empty.tsx +104 -0
  200. package/templates/monorepo-starter/packages/ui/src/components/field.tsx +248 -0
  201. package/templates/monorepo-starter/packages/ui/src/components/form.tsx +197 -0
  202. package/templates/monorepo-starter/packages/ui/src/components/hover-card.tsx +44 -0
  203. package/templates/monorepo-starter/packages/ui/src/components/input-field.tsx +116 -0
  204. package/templates/monorepo-starter/packages/ui/src/components/input-group.tsx +170 -0
  205. package/templates/monorepo-starter/packages/ui/src/components/input-otp.tsx +77 -0
  206. package/templates/monorepo-starter/packages/ui/src/components/input.tsx +44 -0
  207. package/templates/monorepo-starter/packages/ui/src/components/item.tsx +193 -0
  208. package/templates/monorepo-starter/packages/ui/src/components/kbd.tsx +28 -0
  209. package/templates/monorepo-starter/packages/ui/src/components/label.tsx +24 -0
  210. package/templates/monorepo-starter/packages/ui/src/components/menubar.tsx +276 -0
  211. package/templates/monorepo-starter/packages/ui/src/components/native-select.tsx +53 -0
  212. package/templates/monorepo-starter/packages/ui/src/components/navigation-menu.tsx +168 -0
  213. package/templates/monorepo-starter/packages/ui/src/components/otp-modal.tsx +125 -0
  214. package/templates/monorepo-starter/packages/ui/src/components/pagination.tsx +127 -0
  215. package/templates/monorepo-starter/packages/ui/src/components/popover.tsx +48 -0
  216. package/templates/monorepo-starter/packages/ui/src/components/progress.tsx +31 -0
  217. package/templates/monorepo-starter/packages/ui/src/components/radio-group.tsx +45 -0
  218. package/templates/monorepo-starter/packages/ui/src/components/resizable.tsx +53 -0
  219. package/templates/monorepo-starter/packages/ui/src/components/scroll-area.tsx +58 -0
  220. package/templates/monorepo-starter/packages/ui/src/components/select-field.tsx +165 -0
  221. package/templates/monorepo-starter/packages/ui/src/components/select.tsx +190 -0
  222. package/templates/monorepo-starter/packages/ui/src/components/separator.tsx +28 -0
  223. package/templates/monorepo-starter/packages/ui/src/components/sheet.tsx +139 -0
  224. package/templates/monorepo-starter/packages/ui/src/components/sidebar.tsx +728 -0
  225. package/templates/monorepo-starter/packages/ui/src/components/skeleton.tsx +13 -0
  226. package/templates/monorepo-starter/packages/ui/src/components/slider.tsx +63 -0
  227. package/templates/monorepo-starter/packages/ui/src/components/sonner.tsx +57 -0
  228. package/templates/monorepo-starter/packages/ui/src/components/spinner.tsx +16 -0
  229. package/templates/monorepo-starter/packages/ui/src/components/switch-field.tsx +22 -0
  230. package/templates/monorepo-starter/packages/ui/src/components/switch.tsx +35 -0
  231. package/templates/monorepo-starter/packages/ui/src/components/table.tsx +120 -0
  232. package/templates/monorepo-starter/packages/ui/src/components/tabs.tsx +66 -0
  233. package/templates/monorepo-starter/packages/ui/src/components/textarea.tsx +18 -0
  234. package/templates/monorepo-starter/packages/ui/src/components/toggle-group.tsx +83 -0
  235. package/templates/monorepo-starter/packages/ui/src/components/toggle.tsx +47 -0
  236. package/templates/monorepo-starter/packages/ui/src/components/tooltip.tsx +61 -0
  237. package/templates/monorepo-starter/packages/ui/src/hooks/use-confirm.ts +12 -0
  238. package/templates/monorepo-starter/packages/ui/src/hooks/use-dialog.ts +12 -0
  239. package/templates/monorepo-starter/packages/ui/src/hooks/use-mobile.ts +21 -0
  240. package/templates/monorepo-starter/packages/ui/src/lib/utils.ts +38 -3
  241. package/templates/monorepo-starter/packages/ui/src/provider/confirm-dialog.tsx +99 -0
  242. package/templates/monorepo-starter/packages/ui/src/provider/dialog.tsx +62 -0
  243. package/templates/monorepo-starter/packages/ui/src/provider/index.tsx +30 -0
  244. package/templates/monorepo-starter/packages/ui/src/provider/react-query.tsx +11 -0
  245. package/templates/monorepo-starter/{apps/web/src/providers → packages/ui/src/provider}/theme.tsx +5 -7
  246. package/templates/monorepo-starter/packages/ui/src/styles/globals.css +109 -61
  247. package/templates/monorepo-starter/packages/ui/src/types/airports-data.d.ts +21 -0
  248. package/templates/monorepo-starter/packages/ui/tsconfig.json +1 -3
  249. package/templates/monorepo-starter/pnpm-lock.yaml +17765 -12441
  250. package/templates/monorepo-starter/pnpm-workspace.yaml +3 -16
  251. package/templates/monorepo-starter/server/.env.example +7 -5
  252. package/templates/monorepo-starter/server/eslint.config.mjs +1 -4
  253. package/templates/monorepo-starter/server/package.json +129 -97
  254. package/templates/monorepo-starter/server/prisma/schema.prisma +170 -92
  255. package/templates/monorepo-starter/server/prisma/seed-functions.ts +0 -0
  256. package/templates/monorepo-starter/server/prisma/seed.ts +0 -0
  257. package/templates/monorepo-starter/server/prisma.config.ts +9 -9
  258. package/templates/monorepo-starter/server/scripts/admin-bootstrap.ts +61 -0
  259. package/templates/monorepo-starter/server/scripts/gen-module.sh +20 -0
  260. package/templates/monorepo-starter/server/scripts/gen-soft-delete.ts +52 -0
  261. package/templates/monorepo-starter/server/src/app.module.ts +35 -28
  262. package/templates/monorepo-starter/server/src/lib/constants/index.ts +59 -0
  263. package/templates/monorepo-starter/server/src/lib/decorators/boolean-query.decorator.ts +18 -0
  264. package/templates/monorepo-starter/server/src/lib/decorators/logger.decorator.ts +1 -1
  265. package/templates/monorepo-starter/server/src/lib/decorators/oauth.decorator.ts +7 -0
  266. package/templates/monorepo-starter/server/src/lib/decorators/user.decorator.ts +10 -0
  267. package/templates/monorepo-starter/server/src/lib/filters/exceptions.filter.ts +169 -24
  268. package/templates/monorepo-starter/server/src/lib/guards/auth.guard.ts +29 -65
  269. package/templates/monorepo-starter/server/src/lib/guards/oauth.guard.ts +28 -0
  270. package/templates/monorepo-starter/server/src/lib/interceptors/response.interceptor.ts +42 -5
  271. package/templates/monorepo-starter/server/src/lib/middleware/cache.middleware.ts +24 -0
  272. package/templates/monorepo-starter/server/src/lib/schemas/env.schema.ts +25 -5
  273. package/templates/monorepo-starter/server/src/lib/utils/client.utils.ts +95 -0
  274. package/templates/monorepo-starter/server/src/lib/utils/cookie.util.ts +4 -3
  275. package/templates/monorepo-starter/server/src/main.ts +20 -10
  276. package/templates/monorepo-starter/server/src/modules/admin/admin.controller.ts +54 -0
  277. package/templates/monorepo-starter/server/src/modules/admin/admin.module.ts +13 -0
  278. package/templates/monorepo-starter/server/src/modules/admin/admin.service.ts +134 -0
  279. package/templates/monorepo-starter/server/src/modules/auth/auth.controller.ts +60 -18
  280. package/templates/monorepo-starter/server/src/modules/auth/auth.module.ts +3 -2
  281. package/templates/monorepo-starter/server/src/modules/auth/auth.service.ts +354 -307
  282. package/templates/monorepo-starter/server/src/modules/auth/oauth.controller.ts +50 -28
  283. package/templates/monorepo-starter/server/src/modules/auth/oauth.service.ts +59 -34
  284. package/templates/monorepo-starter/server/src/modules/auth/otp.service.ts +29 -36
  285. package/templates/monorepo-starter/server/src/modules/cache/cache.module.ts +28 -0
  286. package/templates/monorepo-starter/server/src/modules/cache/cache.service.ts +33 -0
  287. package/templates/monorepo-starter/server/src/modules/env/env.service.ts +4 -3
  288. package/templates/monorepo-starter/server/src/modules/logger/logger.module.ts +1 -0
  289. package/templates/monorepo-starter/server/src/modules/logger/logger.service.ts +1 -1
  290. package/templates/monorepo-starter/server/src/modules/logger/winston.config.ts +15 -1
  291. package/templates/monorepo-starter/server/src/modules/media/cloudinary.provider.ts +14 -0
  292. package/templates/monorepo-starter/server/src/modules/media/cloudinary.service.ts +62 -0
  293. package/templates/monorepo-starter/server/src/modules/media/media.controller.ts +72 -0
  294. package/templates/monorepo-starter/server/src/modules/media/media.module.ts +13 -0
  295. package/templates/monorepo-starter/server/src/modules/media/media.service.ts +146 -0
  296. package/templates/monorepo-starter/server/src/modules/notification/email.service.ts +30 -0
  297. package/templates/monorepo-starter/server/src/modules/notification/messaging.service.ts +50 -0
  298. package/templates/monorepo-starter/server/src/modules/notification/notification.controller.ts +44 -0
  299. package/templates/monorepo-starter/server/src/modules/notification/notification.module.ts +7 -3
  300. package/templates/monorepo-starter/server/src/modules/notification/notification.service.ts +121 -78
  301. package/templates/monorepo-starter/server/src/modules/notification/push.service.ts +92 -0
  302. package/templates/monorepo-starter/server/src/modules/prisma/prisma.extension.ts +84 -56
  303. package/templates/monorepo-starter/server/src/modules/prisma/prisma.service.ts +10 -9
  304. package/templates/monorepo-starter/server/src/modules/prisma/soft-delete.models.ts +7 -0
  305. package/templates/monorepo-starter/server/src/modules/public/public.controller.ts +3 -2
  306. package/templates/monorepo-starter/server/src/modules/public/public.module.ts +5 -4
  307. package/templates/monorepo-starter/server/src/modules/public/public.service.ts +7 -4
  308. package/templates/monorepo-starter/server/src/modules/scheduler/cleanup.service.ts +15 -7
  309. package/templates/monorepo-starter/server/src/modules/scheduler/scheduler.module.ts +2 -0
  310. package/templates/monorepo-starter/server/src/modules/token/token.module.ts +3 -4
  311. package/templates/monorepo-starter/server/src/modules/token/token.service.ts +205 -76
  312. package/templates/monorepo-starter/server/src/modules/user/user.controller.ts +21 -0
  313. package/templates/monorepo-starter/server/src/modules/user/user.module.ts +12 -0
  314. package/templates/monorepo-starter/server/src/modules/user/user.service.ts +38 -0
  315. package/templates/monorepo-starter/server/src/types/express.d.ts +6 -2
  316. package/templates/monorepo-starter/server/src/types/index.ts +5 -0
  317. package/templates/monorepo-starter/server/tsconfig.json +17 -12
  318. package/templates/monorepo-starter/server/tsup.config.ts +7 -2
  319. package/templates/monorepo-starter/tsconfig.json +1 -1
  320. package/templates/monorepo-starter/turbo.json +1 -1
  321. package/templates/monorepo-starter/apps/web/eslint.config.mjs +0 -4
  322. package/templates/monorepo-starter/apps/web/src/app/(auth)/layout.tsx +0 -7
  323. package/templates/monorepo-starter/apps/web/src/app/globals.css +0 -1
  324. package/templates/monorepo-starter/apps/web/src/components/ThemeSwitch.tsx +0 -34
  325. package/templates/monorepo-starter/apps/web/src/providers/index.tsx +0 -10
  326. package/templates/monorepo-starter/apps/web/src/types/index.d.ts +0 -16
  327. package/templates/monorepo-starter/packages/config/eslint/base.js +0 -31
  328. package/templates/monorepo-starter/packages/config/eslint/nest.js +0 -26
  329. package/templates/monorepo-starter/packages/config/eslint/next.js +0 -39
  330. package/templates/monorepo-starter/packages/config/eslint/react.js +0 -30
  331. package/templates/monorepo-starter/packages/config/typescript/base.json +0 -31
  332. package/templates/monorepo-starter/packages/config/typescript/nest.json +0 -14
  333. package/templates/monorepo-starter/packages/config/typescript/next.json +0 -10
  334. package/templates/monorepo-starter/packages/ui/tsconfig.lint.json +0 -8
  335. package/templates/monorepo-starter/server/README.md +0 -63
  336. package/templates/monorepo-starter/server/scripts/generate.sh +0 -14
  337. package/templates/monorepo-starter/server/src/lib/dto/auth.dto.ts +0 -64
  338. package/templates/monorepo-starter/server/src/lib/dto/security-setting.dto.ts +0 -21
  339. package/templates/monorepo-starter/server/src/lib/pipes/validation.pipe.ts +0 -7
  340. package/templates/monorepo-starter/server/src/lib/templates/notification.templates.ts +0 -243
  341. package/templates/monorepo-starter/server/src/lib/utils/general.util.ts +0 -24
  342. package/templates/monorepo-starter/server/src/modules/auth/role.service.ts +0 -99
  343. package/templates/monorepo-starter/server/src/modules/auth/security-setting.service.ts +0 -102
  344. package/templates/monorepo-starter/server/src/modules/template/template.module.ts +0 -8
  345. package/templates/monorepo-starter/server/src/modules/template/template.service.ts +0 -33
  346. package/templates/monorepo-starter/server/src/types/index.d.ts +0 -13
  347. package/templates/monorepo-starter/server/test/app.e2e-spec.ts +0 -25
  348. package/templates/monorepo-starter/server/test/jest-e2e.json +0 -9
  349. /package/templates/monorepo-starter/{packages/ui/src/hooks → apps/dashboard/public}/.gitkeep +0 -0
@@ -1,58 +1,80 @@
1
- import { Controller, Get, Req, Res, UseGuards } from "@nestjs/common";
1
+ import {
2
+ Get,
3
+ Req,
4
+ Res,
5
+ UseGuards,
6
+ Controller,
7
+ BadRequestException,
8
+ } from "@nestjs/common";
2
9
  import { AuthGuard } from "@nestjs/passport";
3
- import { Public } from "@decorators/public.decorator";
4
10
  import type { Request, Response } from "express";
5
- import { TokenService } from "@modules/token/token.service";
6
- import { PrismaService } from "@modules/prisma/prisma.service";
11
+
12
+ import { AuthService } from "./auth.service";
13
+ import { UseOAuthGuard } from "@/decorators/oauth.decorator";
14
+ import { TokenService } from "@/modules/token/token.service";
15
+ import { PrismaService } from "@/modules/prisma/prisma.service";
7
16
 
8
17
  @Controller("oauth")
9
18
  export class OAuthController {
10
19
  constructor(
20
+ private readonly prisma: PrismaService,
11
21
  private readonly tokenService: TokenService,
12
- private readonly prisma: PrismaService
22
+ private readonly authService: AuthService,
13
23
  ) {}
14
24
 
15
- @Public()
25
+ // ===== GOOGLE =====
26
+
16
27
  @Get("google")
17
- @UseGuards(AuthGuard("google"))
28
+ @UseOAuthGuard("google")
18
29
  googleLogin() {}
19
30
 
20
- @Public()
21
31
  @Get("google/callback")
22
32
  @UseGuards(AuthGuard("google"))
23
- async googleCallback(
24
- @Req() req: Request,
25
- @Res({ passthrough: true }) res: Response
26
- ) {
27
- const user = req.user!;
28
- await this.tokenService.createAuthSession(req, res, user);
29
- await this.prisma.user.update({
30
- where: { id: user.id },
31
- data: { lastLoginAt: new Date() },
32
- });
33
-
34
- return res.redirect("/");
33
+ googleCallback(@Req() req: Request, @Res() res: Response) {
34
+ return this.handleOAuthCallback(req, res);
35
35
  }
36
36
 
37
- @Public()
37
+ // ===== FACEBOOK =====
38
+
38
39
  @Get("facebook")
39
- @UseGuards(AuthGuard("facebook"))
40
+ @UseOAuthGuard("facebook")
40
41
  facebookLogin() {}
41
42
 
42
- @Public()
43
43
  @Get("facebook/callback")
44
44
  @UseGuards(AuthGuard("facebook"))
45
- async facebookCallback(
46
- @Req() req: Request,
47
- @Res({ passthrough: true }) res: Response
48
- ) {
45
+ facebookCallback(@Req() req: Request, @Res() res: Response) {
46
+ return this.handleOAuthCallback(req, res);
47
+ }
48
+
49
+ // ===== SHARED =====
50
+
51
+ private async handleOAuthCallback(req: Request, res: Response) {
49
52
  const user = req.user!;
53
+ const redirectUrl = this.extractRedirectUrl(req);
54
+
55
+ this.authService.checkUserStatus(user.status);
56
+
50
57
  await this.tokenService.createAuthSession(req, res, user);
58
+
51
59
  await this.prisma.user.update({
52
60
  where: { id: user.id },
53
61
  data: { lastLoginAt: new Date() },
54
62
  });
55
63
 
56
- return res.redirect("/");
64
+ return res.redirect(redirectUrl);
65
+ }
66
+
67
+ private extractRedirectUrl(req: Request): string {
68
+ const state = req.query.state;
69
+
70
+ if (typeof state !== "string") {
71
+ throw new BadRequestException("Invalid OAuth state");
72
+ }
73
+
74
+ try {
75
+ return Buffer.from(state, "base64").toString("utf-8");
76
+ } catch {
77
+ throw new BadRequestException("Invalid OAuth state encoding");
78
+ }
57
79
  }
58
80
  }
@@ -12,19 +12,21 @@ import {
12
12
  Strategy as FacebookStrategy,
13
13
  type Profile as FacebookProfile,
14
14
  } from "passport-facebook";
15
- import { EnvService } from "@modules/env/env.service";
16
- import { PrismaService } from "@modules/prisma/prisma.service";
15
+ import { slugify } from "@workspace/shared/utils";
16
+
17
17
  import { OtpService } from "./otp.service";
18
- import { NotificationService } from "@modules/notification/notification.service";
18
+ import { EnvService } from "@/modules/env/env.service";
19
+ import { PrismaService } from "@/modules/prisma/prisma.service";
20
+ import { NotificationService } from "@/modules/notification/notification.service";
19
21
 
20
- interface NormalizedProfile {
21
- provider: "google" | "facebook";
22
+ interface OAuthProfile {
23
+ provider: OAuthProvider;
22
24
  id: string;
23
25
  email: string | null;
24
- displayName: string;
25
26
  firstName: string;
26
27
  lastName: string;
27
- photo?: string;
28
+ displayName: string;
29
+ imageUrl?: string;
28
30
  }
29
31
 
30
32
  @Injectable()
@@ -33,7 +35,7 @@ export class OAuthService implements OnModuleInit {
33
35
  private readonly prisma: PrismaService,
34
36
  private readonly otpService: OtpService,
35
37
  private readonly env: EnvService,
36
- private readonly notifyService: NotificationService
38
+ private readonly notifyService: NotificationService,
37
39
  ) {}
38
40
 
39
41
  onModuleInit() {
@@ -58,8 +60,8 @@ export class OAuthService implements OnModuleInit {
58
60
  } catch (err) {
59
61
  done(err, false);
60
62
  }
61
- }
62
- )
63
+ },
64
+ ),
63
65
  );
64
66
  }
65
67
 
@@ -72,7 +74,6 @@ export class OAuthService implements OnModuleInit {
72
74
  clientSecret: this.env.get("FACEBOOK_CLIENT_SECRET"),
73
75
  callbackURL: this.env.get("FACEBOOK_CALLBACK_URL"),
74
76
  scope: "email",
75
- profileFields: ["id", "emails", "name", "displayName"],
76
77
  },
77
78
  async (_, __, profile: FacebookProfile, done) => {
78
79
  try {
@@ -81,8 +82,8 @@ export class OAuthService implements OnModuleInit {
81
82
  } catch (err) {
82
83
  done(err, null);
83
84
  }
84
- }
85
- )
85
+ },
86
+ ),
86
87
  );
87
88
  }
88
89
 
@@ -95,7 +96,6 @@ export class OAuthService implements OnModuleInit {
95
96
 
96
97
  let user = await this.prisma.user.findUnique({
97
98
  where: { email: normalized.email },
98
- include: { roles: true },
99
99
  });
100
100
 
101
101
  if (!user) {
@@ -105,44 +105,69 @@ export class OAuthService implements OnModuleInit {
105
105
  firstName: normalized.firstName,
106
106
  lastName: normalized.lastName,
107
107
  displayName: normalized.displayName,
108
- imageUrl: normalized.photo,
109
108
  isEmailVerified: true,
110
109
  password: null,
111
- roles: { create: [{ role: "customer" }] },
110
+ role: "customer",
112
111
  },
113
- include: { roles: true },
114
112
  });
115
113
 
116
114
  await this.notifyService.sendNotification({
117
- userId: user.id,
118
- purpose: "signup",
115
+ purpose: "authSignUp",
119
116
  to: user.email!,
120
- metadata: { user },
117
+ meta: { user },
118
+ });
119
+ }
120
+
121
+ if (normalized.imageUrl) {
122
+ await this.prisma.media.upsert({
123
+ where: {
124
+ url: normalized.imageUrl,
125
+ },
126
+ create: {
127
+ type: "photo",
128
+ url: normalized.imageUrl,
129
+ filename: `${slugify(normalized.displayName)}-avatar`,
130
+ title: `${slugify(normalized.displayName)}-avatar`,
131
+ mimeType: "image/jpeg",
132
+ size: 0,
133
+ hash: crypto.randomUUID(),
134
+ uploadedById: user.id,
135
+ },
136
+ update: {
137
+ url: normalized.imageUrl,
138
+ filename: `${slugify(normalized.displayName)}-avatar`,
139
+ mimeType: "image/jpeg",
140
+ size: 0,
141
+ hash: crypto.randomUUID(),
142
+ uploadedById: user.id,
143
+ },
121
144
  });
122
145
  }
123
146
 
124
- if (!user.password) {
147
+ const { password, ...rest } = user;
148
+
149
+ if (!password) {
125
150
  await this.otpService.sendOtp({
126
- userId: user.id,
127
- purpose: "setPassword",
151
+ purpose: "authSetPassword",
128
152
  identifier: normalized.email,
129
- type: "token",
130
- metadata: { user },
153
+ type: "secureToken",
154
+ meta: { user: rest },
131
155
  });
132
156
  }
133
157
 
134
158
  return {
135
- id: user.id,
136
- roles: user.roles.map((r) => r.role),
159
+ id: rest.id,
160
+ role: user.role,
161
+ status: user.status,
137
162
  };
138
163
  }
139
164
 
140
165
  private normalizeProfile(
141
- profile: GoogleProfile | FacebookProfile
142
- ): NormalizedProfile {
143
- const provider = profile.provider as "google" | "facebook";
144
-
166
+ profile: GoogleProfile | FacebookProfile,
167
+ ): OAuthProfile {
168
+ const provider = profile.provider as OAuthProvider;
145
169
  const email = profile.emails?.[0]?.value || null;
170
+
146
171
  const displayName =
147
172
  profile.displayName ||
148
173
  `${profile.name?.givenName || ""} ${profile.name?.familyName || ""}`.trim();
@@ -150,16 +175,16 @@ export class OAuthService implements OnModuleInit {
150
175
  const firstName = profile.name?.givenName || "";
151
176
  const lastName = profile.name?.familyName || "";
152
177
 
153
- const photo = profile.photos?.[0]?.value;
178
+ const imageUrl = profile.photos?.[0]?.value;
154
179
 
155
180
  return {
156
- provider,
157
181
  id: profile.id,
182
+ provider,
158
183
  email,
159
184
  displayName,
160
185
  firstName,
161
186
  lastName,
162
- photo,
187
+ imageUrl,
163
188
  };
164
189
  }
165
190
  }
@@ -1,20 +1,20 @@
1
- import { Injectable, UnauthorizedException } from "@nestjs/common";
2
- import { OtpPurpose, OtpType } from "@prisma/client";
3
- import { PrismaService } from "@modules/prisma/prisma.service";
4
- import { NotificationService } from "@modules/notification/notification.service";
5
- import { expiryDate } from "@utils/general.util";
6
- import { EnvService } from "@modules/env/env.service";
7
1
  import crypto from "crypto";
8
- import { LoggerService } from "@modules/logger/logger.service";
9
- import { InjectLogger } from "@decorators/logger.decorator";
2
+ import { Injectable, UnauthorizedException } from "@nestjs/common";
3
+ import { futureDate } from "@workspace/shared/utils";
4
+
5
+ import { InjectLogger } from "@/decorators/logger.decorator";
6
+ import { PrismaService } from "@/modules/prisma/prisma.service";
7
+ import { EnvService } from "@/modules/env/env.service";
8
+ import { LoggerService } from "@/modules/logger/logger.service";
9
+ import { CacheService } from "@/modules/cache/cache.service";
10
+ import { NotificationService } from "@/modules/notification/notification.service";
10
11
 
11
12
  interface SendOtpPayload {
12
- userId: string;
13
13
  identifier: string;
14
14
  purpose: OtpPurpose;
15
15
  type?: OtpType;
16
16
  notify?: boolean;
17
- metadata?: Record<string, any>;
17
+ meta: EmailTemplateProps;
18
18
  }
19
19
 
20
20
  interface verifyOtpPayload {
@@ -32,20 +32,20 @@ export class OtpService {
32
32
  constructor(
33
33
  private readonly prisma: PrismaService,
34
34
  private readonly notifyService: NotificationService,
35
- private readonly env: EnvService
35
+ private readonly env: EnvService,
36
+ private readonly cache: CacheService,
36
37
  ) {}
37
38
 
38
39
  async sendOtp({
39
- userId,
40
40
  identifier,
41
41
  purpose,
42
- type = "otp",
42
+ type = "numericCode",
43
43
  notify = true,
44
- metadata,
44
+ meta,
45
45
  }: SendOtpPayload) {
46
46
  let otp = await this.prisma.otp.findFirst({
47
47
  where: {
48
- userId,
48
+ userId: meta.user.id,
49
49
  purpose,
50
50
  type,
51
51
  usedAt: null,
@@ -56,36 +56,35 @@ export class OtpService {
56
56
  if (!otp) {
57
57
  otp = await this.prisma.otp.create({
58
58
  data: {
59
- userId,
59
+ userId: meta.user.id,
60
60
  purpose,
61
61
  type,
62
62
  secret: this.generateSecret(type),
63
- expiresAt: expiryDate(this.env.get("OTP_EXP"), true),
63
+ expiresAt: futureDate(this.env.get("OTP_EXP")),
64
+ meta: meta as any,
64
65
  },
65
66
  });
66
67
  }
67
68
 
68
- this.logger.log(`🔢 OTP generated`, {
69
- userId,
70
- purpose,
71
- type,
72
- expiresAt: otp.expiresAt,
73
- context: OtpService.name,
74
- });
75
-
76
69
  if (!notify) return otp;
77
70
 
71
+ const clientUrl = (await this.cache.get("clientUrl")) as string;
72
+
78
73
  await this.notifyService.sendNotification({
79
- userId: userId,
80
74
  purpose,
81
75
  to: identifier,
82
- metadata: { otp, identifier, ...metadata },
76
+ meta: { otp, identifier, ...meta, clientUrl },
83
77
  });
84
78
 
85
79
  return otp;
86
80
  }
87
81
 
88
- async verifyOtp({ userId, secret, purpose, type = "otp" }: verifyOtpPayload) {
82
+ async verifyOtp({
83
+ userId,
84
+ secret,
85
+ purpose,
86
+ type = "numericCode",
87
+ }: verifyOtpPayload) {
89
88
  const otp = await this.prisma.otp.findFirst({
90
89
  where: {
91
90
  userId,
@@ -111,20 +110,14 @@ export class OtpService {
111
110
  data: { usedAt: new Date() },
112
111
  });
113
112
 
114
- this.logger.log(`✅ OTP verified`, {
115
- userId,
116
- purpose,
117
- context: OtpService.name,
118
- });
119
-
120
113
  return otp;
121
114
  }
122
115
 
123
116
  private generateSecret(type: OtpType, prefix = "") {
124
117
  switch (type) {
125
- case "token":
118
+ case "secureToken":
126
119
  return `${prefix}${crypto.randomBytes(32).toString("hex")}`;
127
- case "otp":
120
+ case "numericCode":
128
121
  return crypto.randomInt(100000, 999999).toString();
129
122
  default:
130
123
  throw new Error(`Unsupported type: ${type}`);
@@ -0,0 +1,28 @@
1
+ import KeyvRedis from "@keyv/redis";
2
+ import { CacheModule as NestCacheModule } from "@nestjs/cache-manager";
3
+ import {
4
+ Global,
5
+ Module,
6
+ type MiddlewareConsumer,
7
+ type NestModule,
8
+ } from "@nestjs/common";
9
+
10
+ import { CacheService } from "./cache.service";
11
+ import { CacheContextMiddleware } from "@/middleware/cache.middleware";
12
+
13
+ @Global()
14
+ @Module({
15
+ imports: [
16
+ NestCacheModule.register({
17
+ store: new KeyvRedis("redis://localhost:6379"),
18
+ ttl: 60 * 60,
19
+ }),
20
+ ],
21
+ providers: [CacheService],
22
+ exports: [CacheService],
23
+ })
24
+ export class CacheModule implements NestModule {
25
+ configure(consumer: MiddlewareConsumer) {
26
+ consumer.apply(CacheContextMiddleware).forRoutes("*");
27
+ }
28
+ }
@@ -0,0 +1,33 @@
1
+ import type { Cache } from "cache-manager";
2
+ import { CACHE_MANAGER } from "@nestjs/cache-manager";
3
+ import { Inject, Injectable } from "@nestjs/common";
4
+
5
+ export const CACHE_KEYS = ["clientUrl"] as const;
6
+
7
+ export type CacheKeys = (typeof CACHE_KEYS)[number] | (string & {});
8
+
9
+ @Injectable()
10
+ export class CacheService {
11
+ constructor(@Inject(CACHE_MANAGER) private readonly cache: Cache) {}
12
+
13
+ async get<T>(key: CacheKeys): Promise<T | null> {
14
+ return (await this.cache.get<T>(key)) ?? null;
15
+ }
16
+
17
+ async set<T>(key: CacheKeys, value: T, ttl?: number) {
18
+ await this.cache.set(key, value, ttl);
19
+ }
20
+
21
+ async del(key: CacheKeys) {
22
+ await this.cache.del(key);
23
+ }
24
+
25
+ async wrap<T>(key: CacheKeys, factory: () => Promise<T>, ttl = 60 * 60) {
26
+ const cached = await this.get<T>(key);
27
+ if (cached !== null) return cached;
28
+
29
+ const value = await factory();
30
+ await this.set(key, value, ttl);
31
+ return value;
32
+ }
33
+ }
@@ -1,8 +1,9 @@
1
1
  import { Injectable } from "@nestjs/common";
2
2
  import { ConfigService } from "@nestjs/config";
3
- import type { EnvSchema } from "@schemas/env.schema";
4
- import { LoggerService } from "@modules/logger/logger.service";
5
- import { InjectLogger } from "@decorators/logger.decorator";
3
+
4
+ import type { EnvSchema } from "@/schemas/env.schema";
5
+ import { InjectLogger } from "@/decorators/logger.decorator";
6
+ import { LoggerService } from "@/modules/logger/logger.service";
6
7
 
7
8
  @Injectable()
8
9
  export class EnvService {
@@ -1,5 +1,6 @@
1
1
  import { Global, Module } from "@nestjs/common";
2
2
  import { WinstonModule } from "nest-winston";
3
+
3
4
  import { winstonConfig } from "./winston.config";
4
5
  import { LoggerService } from "./logger.service";
5
6
 
@@ -8,7 +8,7 @@ export class LoggerService {
8
8
 
9
9
  constructor(
10
10
  @Inject(WINSTON_MODULE_PROVIDER)
11
- private readonly winston: Logger
11
+ private readonly winston: Logger,
12
12
  ) {}
13
13
 
14
14
  setContext(context: string) {
@@ -1,15 +1,20 @@
1
1
  import * as winston from "winston";
2
2
  import "winston-daily-rotate-file";
3
3
  import { utilities } from "nest-winston";
4
+ import { appName } from "@workspace/shared/constants";
5
+
6
+ const isProduction = process.env.NODE_ENV === "production";
4
7
 
5
8
  export const winstonConfig = {
6
9
  transports: [
7
10
  new winston.transports.Console({
11
+ level: isProduction ? "info" : "debug",
8
12
  format: winston.format.combine(
9
13
  winston.format.timestamp(),
10
- utilities.format.nestLike("EcomApp", { prettyPrint: true })
14
+ utilities.format.nestLike(appName.short, { prettyPrint: true }),
11
15
  ),
12
16
  }),
17
+
13
18
  new winston.transports.DailyRotateFile({
14
19
  dirname: "logs",
15
20
  filename: "app-%DATE%.log",
@@ -19,6 +24,7 @@ export const winstonConfig = {
19
24
  maxFiles: "14d",
20
25
  level: "info",
21
26
  }),
27
+
22
28
  new winston.transports.DailyRotateFile({
23
29
  dirname: "logs",
24
30
  filename: "error-%DATE%.log",
@@ -29,4 +35,12 @@ export const winstonConfig = {
29
35
  level: "error",
30
36
  }),
31
37
  ],
38
+
39
+ exceptionHandlers: [
40
+ new winston.transports.File({ filename: "logs/exceptions.log" }),
41
+ ],
42
+
43
+ rejectionHandlers: [
44
+ new winston.transports.File({ filename: "logs/rejections.log" }),
45
+ ],
32
46
  };
@@ -0,0 +1,14 @@
1
+ import { v2 as cloudinary } from "cloudinary";
2
+
3
+ export const cloudinaryProvider = {
4
+ provide: "CLOUDINARY",
5
+ useFactory: () => {
6
+ cloudinary.config({
7
+ cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
8
+ api_key: process.env.CLOUDINARY_API_KEY,
9
+ api_secret: process.env.CLOUDINARY_API_SECRET,
10
+ });
11
+
12
+ return cloudinary;
13
+ },
14
+ };
@@ -0,0 +1,62 @@
1
+ import { createHash } from "crypto";
2
+ import * as streamifier from "streamifier";
3
+ import { Inject, Injectable, BadRequestException } from "@nestjs/common";
4
+ import { type UploadApiResponse, v2 as Cloudinary } from "cloudinary";
5
+
6
+ import { PrismaService } from "@/modules/prisma/prisma.service";
7
+
8
+ @Injectable()
9
+ export class CloudinaryService {
10
+ constructor(
11
+ @Inject("CLOUDINARY") private readonly cloudinary: typeof Cloudinary,
12
+ private readonly prisma: PrismaService,
13
+ ) {}
14
+
15
+ async uploadFile(
16
+ file: Express.Multer.File,
17
+ folder = "posts",
18
+ ): Promise<{ data: UploadApiResponse; hash: string }> {
19
+ if (!file) throw new BadRequestException("No file provided");
20
+
21
+ const hash = createHash("sha256").update(file.buffer).digest("hex");
22
+
23
+ const existing = await this.prisma.media.findUnique({
24
+ where: { hash },
25
+ });
26
+
27
+ if (existing) {
28
+ throw new BadRequestException("File already uploaded");
29
+ }
30
+
31
+ const result = await new Promise<UploadApiResponse>((resolve, reject) => {
32
+ const uploadStream = this.cloudinary.uploader.upload_stream(
33
+ {
34
+ folder,
35
+ resource_type: "auto",
36
+ public_id: hash,
37
+ overwrite: false,
38
+ },
39
+ (error, res) => {
40
+ if (error) reject(error);
41
+ else resolve(res!);
42
+ },
43
+ );
44
+
45
+ streamifier.createReadStream(file.buffer).pipe(uploadStream);
46
+ });
47
+
48
+ return { data: result, hash };
49
+ }
50
+
51
+ async deleteFile(publicId: string) {
52
+ const res = await this.cloudinary.uploader.destroy(publicId, {
53
+ resource_type: "auto",
54
+ });
55
+
56
+ if (res.result !== "ok" && res.result !== "not found") {
57
+ throw new BadRequestException("Failed to delete file from Cloudinary");
58
+ }
59
+
60
+ return res;
61
+ }
62
+ }