create-easy-stack 1.0.3

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 (327) hide show
  1. package/bin/create.mjs +244 -0
  2. package/infra/email/files/src/core/config/email.env.ts +73 -0
  3. package/infra/email/files/src/core/infrastructure/email/email-dispatcher.port.ts +13 -0
  4. package/infra/email/files/src/core/infrastructure/email/email-layout.spec.ts +83 -0
  5. package/infra/email/files/src/core/infrastructure/email/email-layout.ts +88 -0
  6. package/infra/email/files/src/core/infrastructure/email/email-sender.factory.spec.ts +87 -0
  7. package/infra/email/files/src/core/infrastructure/email/email-sender.factory.ts +35 -0
  8. package/infra/email/files/src/core/infrastructure/email/email-sender.port.ts +19 -0
  9. package/infra/email/files/src/core/infrastructure/email/email.job.spec.ts +101 -0
  10. package/infra/email/files/src/core/infrastructure/email/email.job.ts +64 -0
  11. package/infra/email/files/src/core/infrastructure/email/email.module.spec.ts +60 -0
  12. package/infra/email/files/src/core/infrastructure/email/email.module.ts +34 -0
  13. package/infra/email/files/src/core/infrastructure/email/log-email-sender.spec.ts +34 -0
  14. package/infra/email/files/src/core/infrastructure/email/log-email-sender.ts +17 -0
  15. package/infra/email/files/src/core/infrastructure/email/smtp-email-sender.spec.ts +99 -0
  16. package/infra/email/files/src/core/infrastructure/email/smtp-email-sender.ts +63 -0
  17. package/infra/email/infra.json +58 -0
  18. package/infra/prisma-postgres/docker.service.yml +12 -0
  19. package/infra/prisma-postgres/files/prisma/schema.prisma +10 -0
  20. package/infra/prisma-postgres/files/prisma.config.mjs +10 -0
  21. package/infra/prisma-postgres/files/src/core/config/database.env.ts +13 -0
  22. package/infra/prisma-postgres/files/src/core/infrastructure/database/database.module.ts +9 -0
  23. package/infra/prisma-postgres/files/src/core/infrastructure/database/prisma.service.ts +26 -0
  24. package/infra/prisma-postgres/infra.json +60 -0
  25. package/infra/rabbitmq/docker.service.yml +16 -0
  26. package/infra/rabbitmq/files/src/core/config/messaging.env.ts +29 -0
  27. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/inline-job-queue.spec.ts +60 -0
  28. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/inline-job-queue.ts +33 -0
  29. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-failure.spec.ts +23 -0
  30. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-failure.ts +19 -0
  31. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-payload-cipher.spec.ts +80 -0
  32. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-payload-cipher.ts +66 -0
  33. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.boundary.spec.ts +34 -0
  34. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.factory.ts +21 -0
  35. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.module.spec.ts +75 -0
  36. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.module.ts +30 -0
  37. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.port.ts +59 -0
  38. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/rabbitmq-job-queue.spec.ts +510 -0
  39. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/rabbitmq-job-queue.ts +353 -0
  40. package/infra/rabbitmq/files/src/core/infrastructure/messaging/rabbitmq/rabbitmq.module.ts +9 -0
  41. package/infra/rabbitmq/files/src/core/infrastructure/messaging/rabbitmq/rabbitmq.service.spec.ts +289 -0
  42. package/infra/rabbitmq/files/src/core/infrastructure/messaging/rabbitmq/rabbitmq.service.ts +207 -0
  43. package/infra/rabbitmq/infra.json +55 -0
  44. package/infra/redis/docker.service.yml +10 -0
  45. package/infra/redis/files/src/core/http/decorators/rate-limit.decorator.ts +26 -0
  46. package/infra/redis/files/src/core/http/interceptors/rate-limit.interceptor.spec.ts +180 -0
  47. package/infra/redis/files/src/core/http/interceptors/rate-limit.interceptor.ts +105 -0
  48. package/infra/redis/files/src/core/infrastructure/redis/cache.constants.ts +67 -0
  49. package/infra/redis/files/src/core/infrastructure/redis/idempotency/idempotency.module.ts +9 -0
  50. package/infra/redis/files/src/core/infrastructure/redis/idempotency/idempotency.service.spec.ts +146 -0
  51. package/infra/redis/files/src/core/infrastructure/redis/idempotency/idempotency.service.ts +131 -0
  52. package/infra/redis/files/src/core/infrastructure/redis/lock/lock.module.ts +9 -0
  53. package/infra/redis/files/src/core/infrastructure/redis/lock/lock.service.integration-spec.ts +111 -0
  54. package/infra/redis/files/src/core/infrastructure/redis/lock/lock.service.ts +82 -0
  55. package/infra/redis/files/src/core/infrastructure/redis/rate-limit/rate-limit.module.ts +15 -0
  56. package/infra/redis/files/src/core/infrastructure/redis/rate-limit/rate-limit.service.spec.ts +45 -0
  57. package/infra/redis/files/src/core/infrastructure/redis/rate-limit/rate-limit.service.ts +38 -0
  58. package/infra/redis/files/src/core/infrastructure/redis/redis.module.ts +9 -0
  59. package/infra/redis/files/src/core/infrastructure/redis/redis.service.ts +196 -0
  60. package/infra/redis/infra.json +53 -0
  61. package/infra/storage-cloudinary/files/src/core/config/storage.env.ts +43 -0
  62. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/cloudinary-image-storage.spec.ts +275 -0
  63. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/cloudinary-image-storage.ts +158 -0
  64. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-storage.factory.ts +30 -0
  65. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-storage.module.ts +17 -0
  66. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-storage.port.ts +38 -0
  67. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-type.spec.ts +48 -0
  68. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-type.ts +25 -0
  69. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/unconfigured-image-storage.ts +19 -0
  70. package/infra/storage-cloudinary/infra.json +73 -0
  71. package/lib/fs-utils.mjs +115 -0
  72. package/lib/registry.mjs +50 -0
  73. package/lib/scaffold.mjs +157 -0
  74. package/package.json +37 -0
  75. package/templates/nest-ddd/files/.env.example +5 -0
  76. package/templates/nest-ddd/files/.husky/commit-msg +1 -0
  77. package/templates/nest-ddd/files/.husky/pre-commit +1 -0
  78. package/templates/nest-ddd/files/.oxlintrc.json +10 -0
  79. package/templates/nest-ddd/files/.prettierrc +4 -0
  80. package/templates/nest-ddd/files/_gitignore +40 -0
  81. package/templates/nest-ddd/files/commitlint.config.js +3 -0
  82. package/templates/nest-ddd/files/nest-cli.json +8 -0
  83. package/templates/nest-ddd/files/package.json +66 -0
  84. package/templates/nest-ddd/files/plop-templates/command/command.ts.hbs +5 -0
  85. package/templates/nest-ddd/files/plop-templates/command/handler.ts.hbs +30 -0
  86. package/templates/nest-ddd/files/plop-templates/module/application/commands/create-{{kebab}}.command.ts.hbs +5 -0
  87. package/templates/nest-ddd/files/plop-templates/module/application/commands/create-{{kebab}}.handler.spec.ts.hbs +35 -0
  88. package/templates/nest-ddd/files/plop-templates/module/application/commands/create-{{kebab}}.handler.ts.hbs +25 -0
  89. package/templates/nest-ddd/files/plop-templates/module/application/index.ts.hbs +5 -0
  90. package/templates/nest-ddd/files/plop-templates/module/application/queries/get-{{kebab}}-by-id.handler.ts.hbs +29 -0
  91. package/templates/nest-ddd/files/plop-templates/module/application/queries/get-{{kebab}}-by-id.query.ts.hbs +5 -0
  92. package/templates/nest-ddd/files/plop-templates/module/domain/entities/{{kebab}}.entity.ts.hbs +37 -0
  93. package/templates/nest-ddd/files/plop-templates/module/domain/events/{{kebab}}-created.event.ts.hbs +10 -0
  94. package/templates/nest-ddd/files/plop-templates/module/domain/exceptions/{{kebab}}.exception.ts.hbs +11 -0
  95. package/templates/nest-ddd/files/plop-templates/module/domain/index.ts.hbs +4 -0
  96. package/templates/nest-ddd/files/plop-templates/module/domain/repositories/{{kebab}}.repository.ts.hbs +7 -0
  97. package/templates/nest-ddd/files/plop-templates/module/presentation/controllers/{{kebab}}.controller.ts.hbs +53 -0
  98. package/templates/nest-ddd/files/plop-templates/module/presentation/dtos/create-{{kebab}}.dto.ts.hbs +10 -0
  99. package/templates/nest-ddd/files/plop-templates/module/presentation/dtos/{{kebab}}-response.dto.ts.hbs +15 -0
  100. package/templates/nest-ddd/files/plop-templates/module/presentation/index.ts.hbs +4 -0
  101. package/templates/nest-ddd/files/plop-templates/module/presentation/mappers/{{kebab}}.mapper.ts.hbs +13 -0
  102. package/templates/nest-ddd/files/plop-templates/module/{{kebab}}.module.ts.hbs +40 -0
  103. package/templates/nest-ddd/files/plop-templates/persistence-memory/infrastructure/database/repositories/in-memory-{{kebab}}.repository.ts.hbs +17 -0
  104. package/templates/nest-ddd/files/plop-templates/persistence-memory/infrastructure/index.ts.hbs +1 -0
  105. package/templates/nest-ddd/files/plop-templates/persistence-prisma/infrastructure/database/mappers/{{kebab}}.mapper.ts.hbs +29 -0
  106. package/templates/nest-ddd/files/plop-templates/persistence-prisma/infrastructure/database/repositories/prisma-{{kebab}}.repository.ts.hbs +28 -0
  107. package/templates/nest-ddd/files/plop-templates/persistence-prisma/infrastructure/index.ts.hbs +2 -0
  108. package/templates/nest-ddd/files/plop-templates/query/handler.ts.hbs +28 -0
  109. package/templates/nest-ddd/files/plop-templates/query/query.ts.hbs +5 -0
  110. package/templates/nest-ddd/files/plop-templates/snippets/error-codes.hbs +14 -0
  111. package/templates/nest-ddd/files/plop-templates/snippets/prisma-model.hbs +7 -0
  112. package/templates/nest-ddd/files/plopfile.mjs +186 -0
  113. package/templates/nest-ddd/files/src/app.controller.ts +13 -0
  114. package/templates/nest-ddd/files/src/app.module.ts +26 -0
  115. package/templates/nest-ddd/files/src/core/config/app.config.ts +9 -0
  116. package/templates/nest-ddd/files/src/core/config/env.validation.ts +30 -0
  117. package/templates/nest-ddd/files/src/core/domain/base/aggregate-root.base.ts +18 -0
  118. package/templates/nest-ddd/files/src/core/domain/base/domain-event.base.ts +7 -0
  119. package/templates/nest-ddd/files/src/core/domain/base/entity.base.ts +36 -0
  120. package/templates/nest-ddd/files/src/core/domain/base/value-object.base.ts +17 -0
  121. package/templates/nest-ddd/files/src/core/domain/exceptions/domain.exception.ts +16 -0
  122. package/templates/nest-ddd/files/src/core/domain/exceptions/error-codes.ts +28 -0
  123. package/templates/nest-ddd/files/src/core/domain/types/pagination.ts +9 -0
  124. package/templates/nest-ddd/files/src/core/http/decorators/client-info.decorator.ts +24 -0
  125. package/templates/nest-ddd/files/src/core/http/decorators/current-user.decorator.ts +19 -0
  126. package/templates/nest-ddd/files/src/core/http/dtos/paginated-response.dto.ts +29 -0
  127. package/templates/nest-ddd/files/src/core/http/dtos/pagination-query.dto.ts +17 -0
  128. package/templates/nest-ddd/files/src/core/http/filters/all-exceptions.filter.spec.ts +163 -0
  129. package/templates/nest-ddd/files/src/core/http/filters/all-exceptions.filter.ts +97 -0
  130. package/templates/nest-ddd/files/src/core/http/guards/max-body-size.guard.spec.ts +47 -0
  131. package/templates/nest-ddd/files/src/core/http/guards/max-body-size.guard.ts +31 -0
  132. package/templates/nest-ddd/files/src/core/http/interceptors/logging.interceptor.ts +37 -0
  133. package/templates/nest-ddd/files/src/core/http/interceptors/response.interceptor.ts +53 -0
  134. package/templates/nest-ddd/files/src/core/http/logging/app.logger.ts +34 -0
  135. package/templates/nest-ddd/files/src/core/index.ts +33 -0
  136. package/templates/nest-ddd/files/src/core/utils/correlation-id.util.spec.ts +29 -0
  137. package/templates/nest-ddd/files/src/core/utils/correlation-id.util.ts +15 -0
  138. package/templates/nest-ddd/files/src/core/utils/hash.util.spec.ts +61 -0
  139. package/templates/nest-ddd/files/src/core/utils/hash.util.ts +47 -0
  140. package/templates/nest-ddd/files/src/core/utils/snowflake.util.spec.ts +40 -0
  141. package/templates/nest-ddd/files/src/core/utils/snowflake.util.ts +99 -0
  142. package/templates/nest-ddd/files/src/core/utils/string.util.spec.ts +20 -0
  143. package/templates/nest-ddd/files/src/core/utils/string.util.ts +21 -0
  144. package/templates/nest-ddd/files/src/core/utils/time.util.spec.ts +25 -0
  145. package/templates/nest-ddd/files/src/core/utils/time.util.ts +25 -0
  146. package/templates/nest-ddd/files/src/core/utils/ttl.util.ts +22 -0
  147. package/templates/nest-ddd/files/src/main.ts +61 -0
  148. package/templates/nest-ddd/files/test/setup.ts +6 -0
  149. package/templates/nest-ddd/files/tsconfig.build.json +9 -0
  150. package/templates/nest-ddd/files/tsconfig.json +28 -0
  151. package/templates/nest-ddd/files/vitest.config.e2e.ts +15 -0
  152. package/templates/nest-ddd/files/vitest.config.ts +16 -0
  153. package/templates/nest-ddd/template.json +8 -0
  154. package/templates/node-express/files/.env.example +28 -0
  155. package/templates/node-express/files/docker-compose.yml +52 -0
  156. package/templates/node-express/files/package-lock.json +11880 -0
  157. package/templates/node-express/files/package.json +43 -0
  158. package/templates/node-express/files/server.js +13 -0
  159. package/templates/node-express/files/src/app.js +72 -0
  160. package/templates/node-express/files/src/auth/authUtils.js +116 -0
  161. package/templates/node-express/files/src/auth/checkAuth.js +61 -0
  162. package/templates/node-express/files/src/bucket_pattern/comment.model.js +27 -0
  163. package/templates/node-express/files/src/bucket_pattern/comment.service.js +50 -0
  164. package/templates/node-express/files/src/config/config.aws-s3.js +28 -0
  165. package/templates/node-express/files/src/config/config.cloudinary.js +13 -0
  166. package/templates/node-express/files/src/config/config.mongodb.js +25 -0
  167. package/templates/node-express/files/src/config/config.multer.js +25 -0
  168. package/templates/node-express/files/src/config/config.nodemailer.js +16 -0
  169. package/templates/node-express/files/src/controllers/access.controller.js +49 -0
  170. package/templates/node-express/files/src/controllers/cart.controller.js +38 -0
  171. package/templates/node-express/files/src/controllers/checkout.controller.js +15 -0
  172. package/templates/node-express/files/src/controllers/comment.controller.js +29 -0
  173. package/templates/node-express/files/src/controllers/discount.controller.js +68 -0
  174. package/templates/node-express/files/src/controllers/email.controller.js +16 -0
  175. package/templates/node-express/files/src/controllers/inventory.controller.js +15 -0
  176. package/templates/node-express/files/src/controllers/notification.controller.js +15 -0
  177. package/templates/node-express/files/src/controllers/product.controller.js +175 -0
  178. package/templates/node-express/files/src/controllers/profile.controller.js +65 -0
  179. package/templates/node-express/files/src/controllers/rbac.controller.js +41 -0
  180. package/templates/node-express/files/src/controllers/template.controller.js +20 -0
  181. package/templates/node-express/files/src/controllers/upload.controller.js +56 -0
  182. package/templates/node-express/files/src/controllers/user.controller.js +36 -0
  183. package/templates/node-express/files/src/core/error.response.js +84 -0
  184. package/templates/node-express/files/src/core/success.response.js +44 -0
  185. package/templates/node-express/files/src/database/init.mongodb.level0.js +16 -0
  186. package/templates/node-express/files/src/database/init.mongodb.level1.js +36 -0
  187. package/templates/node-express/files/src/database/init.redis.js +119 -0
  188. package/templates/node-express/files/src/helpers/asyncHandler.js +7 -0
  189. package/templates/node-express/files/src/helpers/check.connect.js +29 -0
  190. package/templates/node-express/files/src/keys/private_key.pem +28 -0
  191. package/templates/node-express/files/src/keys/public_key.pem +9 -0
  192. package/templates/node-express/files/src/logger/discord.log.js +26 -0
  193. package/templates/node-express/files/src/logger/discord.log.v2.js +56 -0
  194. package/templates/node-express/files/src/logger/mylogger.log.js +130 -0
  195. package/templates/node-express/files/src/logger/winston.log.js +27 -0
  196. package/templates/node-express/files/src/middlewares/index.js +21 -0
  197. package/templates/node-express/files/src/middlewares/rbac.js +37 -0
  198. package/templates/node-express/files/src/middlewares/role.middleware.js +12 -0
  199. package/templates/node-express/files/src/models/KeyToken.model.js +37 -0
  200. package/templates/node-express/files/src/models/apiKey.model.js +31 -0
  201. package/templates/node-express/files/src/models/cart.model.js +47 -0
  202. package/templates/node-express/files/src/models/comment.model.js +43 -0
  203. package/templates/node-express/files/src/models/discount.model.js +92 -0
  204. package/templates/node-express/files/src/models/inventory.model.js +42 -0
  205. package/templates/node-express/files/src/models/notification.model.js +52 -0
  206. package/templates/node-express/files/src/models/order.model.js +68 -0
  207. package/templates/node-express/files/src/models/otp.model.js +32 -0
  208. package/templates/node-express/files/src/models/product.model.js +144 -0
  209. package/templates/node-express/files/src/models/repository/cart.repo.js +71 -0
  210. package/templates/node-express/files/src/models/repository/discount.repo.js +63 -0
  211. package/templates/node-express/files/src/models/repository/inventory.repo.js +45 -0
  212. package/templates/node-express/files/src/models/repository/product.repo.js +145 -0
  213. package/templates/node-express/files/src/models/repository/shop.repo.js +18 -0
  214. package/templates/node-express/files/src/models/repository/spu.repo.js +18 -0
  215. package/templates/node-express/files/src/models/repository/user.repo.js +20 -0
  216. package/templates/node-express/files/src/models/resource.model.js +32 -0
  217. package/templates/node-express/files/src/models/role.model.js +57 -0
  218. package/templates/node-express/files/src/models/shop.model.js +43 -0
  219. package/templates/node-express/files/src/models/sku.model.js +81 -0
  220. package/templates/node-express/files/src/models/spu.model.js +101 -0
  221. package/templates/node-express/files/src/models/template.model.js +25 -0
  222. package/templates/node-express/files/src/models/user.model.js +82 -0
  223. package/templates/node-express/files/src/postman/access.post.http +22 -0
  224. package/templates/node-express/files/src/routes/access/index.route.js +21 -0
  225. package/templates/node-express/files/src/routes/cart/index.route.js +20 -0
  226. package/templates/node-express/files/src/routes/checkout/index.route.js +10 -0
  227. package/templates/node-express/files/src/routes/comment/index.route.js +16 -0
  228. package/templates/node-express/files/src/routes/discount/index.route.js +26 -0
  229. package/templates/node-express/files/src/routes/index.route.js +27 -0
  230. package/templates/node-express/files/src/routes/inventory/index.route.js +11 -0
  231. package/templates/node-express/files/src/routes/notification/index.route.js +14 -0
  232. package/templates/node-express/files/src/routes/product/index.route.js +38 -0
  233. package/templates/node-express/files/src/routes/profile/index.route.js +15 -0
  234. package/templates/node-express/files/src/routes/rbac/index.route.js +21 -0
  235. package/templates/node-express/files/src/routes/template/index.route.js +11 -0
  236. package/templates/node-express/files/src/routes/upload/index.route.js +28 -0
  237. package/templates/node-express/files/src/routes/user/index.route.js +13 -0
  238. package/templates/node-express/files/src/services/access.service.js +257 -0
  239. package/templates/node-express/files/src/services/apiKey.service.js +15 -0
  240. package/templates/node-express/files/src/services/cart.service.js +115 -0
  241. package/templates/node-express/files/src/services/checkout.service.js +218 -0
  242. package/templates/node-express/files/src/services/comment.service.js +185 -0
  243. package/templates/node-express/files/src/services/discount.service.js +282 -0
  244. package/templates/node-express/files/src/services/email.service.js +71 -0
  245. package/templates/node-express/files/src/services/inventory.service.js +35 -0
  246. package/templates/node-express/files/src/services/keyToken.service.js +77 -0
  247. package/templates/node-express/files/src/services/notification.service.js +58 -0
  248. package/templates/node-express/files/src/services/otp.service.js +36 -0
  249. package/templates/node-express/files/src/services/product.service.js +228 -0
  250. package/templates/node-express/files/src/services/rbac.service.js +143 -0
  251. package/templates/node-express/files/src/services/redis.service.js +57 -0
  252. package/templates/node-express/files/src/services/redisPubSub.service.js +72 -0
  253. package/templates/node-express/files/src/services/shop.service.js +21 -0
  254. package/templates/node-express/files/src/services/sku.service.js +52 -0
  255. package/templates/node-express/files/src/services/spu.service.js +89 -0
  256. package/templates/node-express/files/src/services/template.service.js +41 -0
  257. package/templates/node-express/files/src/services/upload.s3.service.js +55 -0
  258. package/templates/node-express/files/src/services/upload.service.js +74 -0
  259. package/templates/node-express/files/src/services/user.service.js +92 -0
  260. package/templates/node-express/files/src/temp/1734420604996-ci-cd.png +0 -0
  261. package/templates/node-express/files/src/temp/1734421222213-cover-fb.jpg +0 -0
  262. package/templates/node-express/files/src/temp/1734421222214-cover-fb-01.jpg +0 -0
  263. package/templates/node-express/files/src/temp/1734421293437-cover-fb-01.jpg +0 -0
  264. package/templates/node-express/files/src/temp/1734421293437-cover-fb.jpg +0 -0
  265. package/templates/node-express/files/src/tests/inventory.test.js +21 -0
  266. package/templates/node-express/files/src/tests/message_queue/kafka/consumer.js +23 -0
  267. package/templates/node-express/files/src/tests/message_queue/kafka/producer.js +21 -0
  268. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/consumer.js +28 -0
  269. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/ordered.consumer.js +26 -0
  270. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/ordered.producer.js +31 -0
  271. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/producer.js +26 -0
  272. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/producerDLX.js +44 -0
  273. package/templates/node-express/files/src/tests/mysql/connect.js +59 -0
  274. package/templates/node-express/files/src/tests/product.test.js +14 -0
  275. package/templates/node-express/files/src/utils/index.js +78 -0
  276. package/templates/node-express/files/src/utils/reasonPhrases.js +344 -0
  277. package/templates/node-express/files/src/utils/statusCodes.js +344 -0
  278. package/templates/node-express/files/src/utils/template.html.js +38 -0
  279. package/templates/node-express/template.json +11 -0
  280. package/templates/react-fsd/files/index.html +13 -0
  281. package/templates/react-fsd/files/package.json +15 -0
  282. package/templates/react-fsd/files/public/favicon.svg +1 -0
  283. package/templates/react-fsd/files/public/icons.svg +24 -0
  284. package/templates/react-fsd/files/src/assets/hero.png +0 -0
  285. package/templates/react-fsd/files/src/assets/typescript.svg +1 -0
  286. package/templates/react-fsd/files/src/assets/vite.svg +1 -0
  287. package/templates/react-fsd/files/src/counter.ts +9 -0
  288. package/templates/react-fsd/files/src/main.ts +60 -0
  289. package/templates/react-fsd/files/src/style.css +296 -0
  290. package/templates/react-fsd/files/tsconfig.json +24 -0
  291. package/templates/react-fsd/template.json +11 -0
  292. package/templates/react-monolithic/files/index.html +13 -0
  293. package/templates/react-monolithic/files/package.json +15 -0
  294. package/templates/react-monolithic/files/public/favicon.svg +1 -0
  295. package/templates/react-monolithic/files/public/icons.svg +24 -0
  296. package/templates/react-monolithic/files/src/assets/hero.png +0 -0
  297. package/templates/react-monolithic/files/src/assets/typescript.svg +1 -0
  298. package/templates/react-monolithic/files/src/assets/vite.svg +1 -0
  299. package/templates/react-monolithic/files/src/counter.ts +9 -0
  300. package/templates/react-monolithic/files/src/main.ts +60 -0
  301. package/templates/react-monolithic/files/src/style.css +296 -0
  302. package/templates/react-monolithic/files/tsconfig.json +24 -0
  303. package/templates/react-monolithic/template.json +11 -0
  304. package/templates/vue-fsd/files/index.html +13 -0
  305. package/templates/vue-fsd/files/package.json +15 -0
  306. package/templates/vue-fsd/files/public/favicon.svg +1 -0
  307. package/templates/vue-fsd/files/public/icons.svg +24 -0
  308. package/templates/vue-fsd/files/src/assets/hero.png +0 -0
  309. package/templates/vue-fsd/files/src/assets/typescript.svg +1 -0
  310. package/templates/vue-fsd/files/src/assets/vite.svg +1 -0
  311. package/templates/vue-fsd/files/src/counter.ts +9 -0
  312. package/templates/vue-fsd/files/src/main.ts +60 -0
  313. package/templates/vue-fsd/files/src/style.css +296 -0
  314. package/templates/vue-fsd/files/tsconfig.json +24 -0
  315. package/templates/vue-fsd/template.json +11 -0
  316. package/templates/vue-monolithic/files/index.html +13 -0
  317. package/templates/vue-monolithic/files/package.json +15 -0
  318. package/templates/vue-monolithic/files/public/favicon.svg +1 -0
  319. package/templates/vue-monolithic/files/public/icons.svg +24 -0
  320. package/templates/vue-monolithic/files/src/assets/hero.png +0 -0
  321. package/templates/vue-monolithic/files/src/assets/typescript.svg +1 -0
  322. package/templates/vue-monolithic/files/src/assets/vite.svg +1 -0
  323. package/templates/vue-monolithic/files/src/counter.ts +9 -0
  324. package/templates/vue-monolithic/files/src/main.ts +60 -0
  325. package/templates/vue-monolithic/files/src/style.css +296 -0
  326. package/templates/vue-monolithic/files/tsconfig.json +24 -0
  327. package/templates/vue-monolithic/template.json +11 -0
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+
3
+ const { NotFoundError, BadRequestError } = require("../core/error.response");
4
+ const orderModel = require("../models/order.model");
5
+ const { findCartById } = require("../models/repository/cart.repo");
6
+ const { checkProductServer } = require("../models/repository/product.repo");
7
+ const { removeAllProductsFromCart } = require("./cart.service");
8
+ const { getDiscountAmount } = require("./discount.service");
9
+ const { acquireLock, releaseLock } = require("./redis.service");
10
+
11
+ class CheckoutService {
12
+ /*
13
+ {
14
+ cartId,
15
+ userId,
16
+ shop_order_ids: [
17
+ {
18
+ shopId,
19
+ shop_discount: [],
20
+ item_products: [
21
+ {
22
+ price,
23
+ quantity,
24
+ productId
25
+ }
26
+ ]
27
+ },
28
+ {
29
+ shopId,
30
+ shop_discount: [
31
+ {
32
+ shopId,
33
+ discountId,
34
+ discountCode
35
+ }
36
+ ],
37
+ item_products: [
38
+ {
39
+ price,
40
+ quantity,
41
+ productId
42
+ }
43
+ ]
44
+ }
45
+ ]
46
+ }
47
+ */
48
+ static async checkoutReview({ cartId, userId, shop_order_ids }) {
49
+ // Check cartId ton tai hay khong
50
+ const foundCart = await findCartById(cartId);
51
+ if (!foundCart) {
52
+ throw new NotFoundError("Cart does not exist");
53
+ }
54
+
55
+ // tinh toan checkout_order
56
+ const checkout_order = {
57
+ total_price: 0,
58
+ total_discount: 0,
59
+ fee_ship: 0,
60
+ total_paid: 0,
61
+ };
62
+
63
+ const shop_order_ids_new = [];
64
+
65
+ // Tinh tong tien total_price
66
+ // Moi item trong shop_order_ids chua cac san pham cua cung 1 shop
67
+ for (let i = 0; i < shop_order_ids.length; i++) {
68
+ const {
69
+ shopId,
70
+ shop_discounts = [],
71
+ item_products = [],
72
+ } = shop_order_ids[i];
73
+ // check tung san pham trong order cua tung shop co ton tai hay khong
74
+ const checkProduct = await checkProductServer(item_products);
75
+ checkProduct.forEach((product) => {
76
+ if (!product) throw new BadRequestError("Order Wrong");
77
+ });
78
+ // tinh tong tien don hang ban dau cua tung shop
79
+ const checkoutPrice = checkProduct.reduce((total, product) => {
80
+ return total + product.quantity * product.price;
81
+ }, 0);
82
+ checkout_order.total_price += checkoutPrice;
83
+ const itemCheckout = {
84
+ shopId,
85
+ shop_discounts,
86
+ priceBeforeDiscount: checkoutPrice,
87
+ priceApplyDiscount: checkoutPrice,
88
+ item_products: checkProduct,
89
+ };
90
+ // check su hop le cua shop_discounts
91
+ if (shop_discounts.length > 0) {
92
+ for (const discount of shop_discounts) {
93
+ const { discount_amount } = await getDiscountAmount({
94
+ code: discount.code,
95
+ userId,
96
+ shopId,
97
+ products: checkProduct,
98
+ });
99
+ if (discount_amount > 0) {
100
+ checkout_order.total_discount += discount_amount;
101
+ itemCheckout.priceApplyDiscount = checkoutPrice - discount_amount;
102
+ }
103
+ }
104
+ }
105
+
106
+ // tong thanh toan cuoi cung cua tung shop
107
+ checkout_order.total_paid += itemCheckout.priceApplyDiscount;
108
+ shop_order_ids_new.push(itemCheckout);
109
+ }
110
+
111
+ // tong thanh toan cuoi cung (tong tien - tong discount + tong fee ship)
112
+ checkout_order.total_paid += checkout_order.fee_ship;
113
+ return {
114
+ shop_order_ids,
115
+ shop_order_ids_new,
116
+ checkout_order,
117
+ };
118
+ }
119
+
120
+ // ~ place_order
121
+ static async placeOrderByUser({
122
+ shop_order_ids,
123
+ cartId,
124
+ userId,
125
+ user_address = {},
126
+ user_payment = {},
127
+ }) {
128
+ // review again
129
+ const { shop_order_ids_new, checkout_order } = await this.checkoutReview({
130
+ cartId,
131
+ userId,
132
+ shop_order_ids,
133
+ });
134
+ // check lai mot lan nua xem san pham co vuot ton kho hay khong
135
+ // get new array products (dung flatMap)
136
+ const products = shop_order_ids_new.flatMap((order) => order.item_products);
137
+ const acquireProducts = [];
138
+ for (const product of products) {
139
+ const { productId, quantity } = product;
140
+ // su dung optimistic, pessimitic => kiem tra ton kho qua ban
141
+ const keyLock = await acquireLock(productId, quantity, cartId);
142
+ acquireProducts.push(keyLock ? true : false);
143
+ if (keyLock) {
144
+ await releaseLock(keyLock);
145
+ }
146
+ }
147
+
148
+ // check if co mot san pham het hang trong kho
149
+ if (acquireProducts.includes(false)) {
150
+ throw new BadRequestError(
151
+ "Mot so san pham da duoc cap nhat, vui long quay lai gio hang"
152
+ );
153
+ }
154
+
155
+ // Neu thanh cong het => tao mot new order
156
+ const newOrder = new orderModel({
157
+ order_user_id: userId,
158
+ order_checkout: checkout_order,
159
+ order_products: shop_order_ids_new,
160
+ order_payment: {},
161
+ order_shipping: {
162
+ city: "Danang",
163
+ country: "Vietnam",
164
+ state: "Danang",
165
+ street: "14 Doan Uan",
166
+ },
167
+ });
168
+ await newOrder.save();
169
+
170
+ // Neu tao order thanh cong => remove product trong cart
171
+ await removeAllProductsFromCart({ cartId, userId });
172
+
173
+ return newOrder;
174
+ }
175
+
176
+ // User lay many order da dat
177
+ static async getOrdersByUser({}) {}
178
+
179
+ // User lay one order da dat
180
+ static async getOrderByUser({}) {}
181
+
182
+ // User cancel order
183
+ static async cancelOrderByUser({}) {}
184
+
185
+ // Cap nhat order
186
+ // Luu y: Cap nhat status van chuyen la do he thong + 3rd-party chu khong phai do shop
187
+ static async updateOrderByShop({}) {}
188
+ }
189
+
190
+ module.exports = CheckoutService;
191
+
192
+
193
+ /*
194
+ Luồng đi từ lúc user click vào xem chi tiết giỏ hàng
195
+ 1. Xem chi tiết giỏ hàng => lúc này user có thể update quantity, xóa product...
196
+ 2. Chuyển qua trang checkout
197
+ - Khi user chuyển qua trang checkout, FE + BE không cho phép cập nhật danh sách sản phẩm nữa
198
+ - User có quyền điền thông tin vận chuyển, chọn hình thức thanh toán, chọn các voucher discount nếu có
199
+ - BE sẽ kiểm tra thông tin giỏ hàng lại một lần nữa
200
+ + BE kiểm tra sản phẩm có thật sự tồn tại không, giá đúng không, số lượng còn không
201
+ + BE tính toán trả ra giá trị đơn hàng, giá trị chưa và đã có discount, tổng discount, phí ship
202
+ 3. Tiến hành đặt hàng
203
+ - BE sẽ check lại thông tin sản phẩm đơn hàng một lần nữa
204
+ + Sử dụng khóa lạc quan để chặn không cho user khác đặt khi user này đang đặt hàng
205
+ + Sử dụng khóa lạc quan kiểm tra xem sản phẩm thật sự còn hàng trong kho không, chỉ cho phép đặt hàng nếu tất cả sản phẩm còn hàng, nếu một trong
206
+ các sản phẩm hết hàng => yêu cầu user quay lại trang giỏ hàng để cập nhật
207
+ + Sử dụng khóa lạc quan => kiểm tra cập nhật tức thời của sản phẩm
208
+ - BE thêm reserve sản phẩm trong kho hàng
209
+ - BE tính toán lại giá cả lần nữa
210
+ => Nếu tất cả thành công
211
+ - BE tạo order
212
+ - BE xóa sản phẩm trong giỏ hàng
213
+ 4. Order còn có các chức năng
214
+ - Cập nhật status (do hệ thống cập nhật trạng thái vận chuyển, còn trạng thái đang xử lí ... có thể do shop tự làm hoặc tự động do hệ thống)
215
+ - Hủy đơn hàng
216
+ - User có thể xem thông tin chi tiết đơn hàng
217
+ ....
218
+ */
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+
3
+ const { NotFoundError } = require("../core/error.response");
4
+ const commentModel = require("../models/comment.model");
5
+ const { getProductById } = require("../models/repository/product.repo");
6
+ const { convertToObjectId } = require("../utils");
7
+
8
+ /*
9
+ + add comment [User, Shop]
10
+ + get a list of comments [User, Shop]
11
+ + delete a comment [User, Shop, Admin]
12
+ */
13
+ class CommentService {
14
+ static async createComment({
15
+ productId,
16
+ userId,
17
+ content,
18
+ parentCommentId = null,
19
+ }) {
20
+ const comment = new commentModel({
21
+ comment_productId: productId,
22
+ comment_userId: userId,
23
+ comment_content: content,
24
+ comment_parentId: parentCommentId,
25
+ });
26
+
27
+ let rightValue;
28
+ if (parentCommentId) {
29
+ // reply comment
30
+ const parentComment = await commentModel.findById(parentCommentId);
31
+ if (!parentComment) {
32
+ throw new NotFoundError("Parent comment not found");
33
+ }
34
+ rightValue = parentComment.comment_right;
35
+
36
+ // updateMany comments
37
+ await Promise.all([
38
+ commentModel.updateMany(
39
+ {
40
+ comment_productId: convertToObjectId(productId),
41
+ comment_right: { $gte: rightValue },
42
+ },
43
+ {
44
+ $inc: {
45
+ comment_right: 2,
46
+ },
47
+ }
48
+ ),
49
+ commentModel.updateMany(
50
+ {
51
+ comment_productId: convertToObjectId(productId),
52
+ comment_left: { $gt: rightValue },
53
+ },
54
+ {
55
+ $inc: {
56
+ comment_left: 2,
57
+ },
58
+ }
59
+ ),
60
+ ]);
61
+ } else {
62
+ // insert a new comment
63
+ const maxRightValue = await commentModel.findOne(
64
+ {
65
+ comment_productId: convertToObjectId(productId),
66
+ },
67
+ "comment_right",
68
+ { sort: { comment_right: -1 } }
69
+ );
70
+ if (maxRightValue) {
71
+ rightValue = maxRightValue.comment_right + 1;
72
+ } else {
73
+ rightValue = 1;
74
+ }
75
+ }
76
+
77
+ // insert comment
78
+ comment.comment_left = rightValue;
79
+ comment.comment_right = rightValue + 1;
80
+
81
+ await comment.save();
82
+ return comment;
83
+ }
84
+
85
+ static async getCommentsByParentId({
86
+ productId,
87
+ parentCommentId = null,
88
+ limit = 50,
89
+ offset = 0,
90
+ }) {
91
+ let comments;
92
+ if (parentCommentId) {
93
+ const parent = await commentModel.findById(parentCommentId);
94
+ if (!parent) {
95
+ throw new NotFoundError("Not found comment for product");
96
+ }
97
+ comments = await commentModel
98
+ .find({
99
+ comment_parentId: convertToObjectId(parentCommentId),
100
+ comment_productId: convertToObjectId(productId),
101
+ comment_left: { $gt: parent.comment_left },
102
+ comment_right: { $lt: parent.comment_right },
103
+ })
104
+ .select({
105
+ comment_left: 1,
106
+ comment_right: 1,
107
+ comment_content: 1,
108
+ comment_userId: 1,
109
+ comment_parentId: 1,
110
+ })
111
+ .skip(offset)
112
+ .limit(limit)
113
+ .sort({ createdAt: 1 });
114
+ } else {
115
+ comments = await commentModel
116
+ .find({
117
+ comment_parentId: parentCommentId,
118
+ comment_productId: convertToObjectId(productId),
119
+ })
120
+ .select({
121
+ comment_left: 1,
122
+ comment_right: 1,
123
+ comment_content: 1,
124
+ comment_userId: 1,
125
+ comment_parentId: 1,
126
+ })
127
+ .skip(offset)
128
+ .limit(limit)
129
+ .sort({ createdAt: 1 });
130
+ }
131
+ return comments;
132
+ }
133
+
134
+ static async deleteComment({ commentId, productId }) {
135
+ // check product exists in the db
136
+ const foundProduct = await getProductById({ productId });
137
+ if (!foundProduct) {
138
+ throw new NotFoundError("Product not found");
139
+ }
140
+ // 1. Xac dinh left va right cua commentId
141
+ const comment = await commentModel.findById(commentId);
142
+ if (!comment) {
143
+ throw new NotFoundError("Comment not found");
144
+ }
145
+ const left = comment.comment_left;
146
+ const right = comment.comment_right;
147
+ // 2. Tinh width = right most - left most + 1
148
+ const width = right - left + 1;
149
+ // 3. Xoa tat cac commentId con (bao gom ca chinh no)
150
+ await commentModel.deleteMany({
151
+ comment_productId: convertToObjectId(productId),
152
+ comment_left: { $gte: left, $lte: right },
153
+ });
154
+ // 4. Cap nhat left, right cho cac node con lai trong cay
155
+ // Cap nhat right_value cua cac node to tien cua node bi xoa
156
+ // Cap nhat right, left cuar cac node nam ben phai cua cay node bi xoa
157
+ await Promise.all([
158
+ commentModel.updateMany(
159
+ {
160
+ comment_productId: convertToObjectId(productId),
161
+ comment_right: { $gt: right },
162
+ },
163
+ {
164
+ $inc: {
165
+ comment_right: -width,
166
+ },
167
+ }
168
+ ),
169
+ commentModel.updateMany(
170
+ {
171
+ comment_productId: convertToObjectId(productId),
172
+ comment_left: { $gt: right },
173
+ },
174
+ {
175
+ $inc: {
176
+ comment_left: -width,
177
+ },
178
+ }
179
+ ),
180
+ ]);
181
+ return true;
182
+ }
183
+ }
184
+
185
+ module.exports = CommentService;
@@ -0,0 +1,282 @@
1
+ /**
2
+ * 1. Generate discount code [Admin | Shop]
3
+ * 2. Get all discount codes [User | Shop]
4
+ * 3. Get all products by discount code [User]
5
+ * 4. Get discount amount [User]
6
+ * 5. Delete discount code [Admin | Shop]
7
+ * 6. Cancel discount code [User]
8
+ */
9
+
10
+ "use strict";
11
+
12
+ const { Types } = require("mongoose");
13
+ const { BadRequestError, NotFoundError } = require("../core/error.response");
14
+ const discountModel = require("../models/discount.model");
15
+ const {
16
+ convertToObjectId,
17
+ updateNestedObjectParser,
18
+ removeBadValue,
19
+ } = require("../utils");
20
+ const { findAllProducts } = require("../models/repository/product.repo");
21
+ const {
22
+ findAllDiscountCodeUnselect,
23
+ updateDiscountByCode,
24
+ checkDiscountExists,
25
+ } = require("../models/repository/discount.repo");
26
+
27
+ class DiscountService {
28
+ static async createDiscountCode(payload) {
29
+ const {
30
+ code,
31
+ start_date,
32
+ end_date,
33
+ is_active,
34
+ shop_id,
35
+ applied_for,
36
+ min_order_value,
37
+ product_ids = [],
38
+ name,
39
+ description,
40
+ type,
41
+ value,
42
+ max_uses,
43
+ used_count,
44
+ max_uses_per_user,
45
+ } = payload;
46
+ // Kiem tra
47
+ console.log(">>> payload: ", payload);
48
+
49
+ if (new Date() < new Date(start_date) || new Date() > new Date(end_date)) {
50
+ throw new BadRequestError("Discount code has expired");
51
+ }
52
+ if (new Date(start_date) >= new Date(end_date)) {
53
+ throw new BadRequestError("Start_date must be before end_date");
54
+ }
55
+ // Tao discount
56
+ const foundDiscount = await discountModel
57
+ .findOne({
58
+ discount_code: code,
59
+ discount_shop_id: convertToObjectId(shop_id),
60
+ })
61
+ .lean();
62
+ if (foundDiscount && foundDiscount.discount_is_active) {
63
+ throw new BadRequestError("Discount code already exists");
64
+ }
65
+ const newDiscount = await discountModel.create({
66
+ discount_name: name,
67
+ discount_code: code,
68
+ discount_description: description,
69
+ discount_type: type,
70
+ discount_value: value,
71
+ discount_min_order_value: min_order_value,
72
+ discount_start_date: new Date(start_date),
73
+ discount_end_date: new Date(end_date),
74
+ discount_is_active: is_active,
75
+ discount_applied_for: applied_for,
76
+ discount_max_uses_per_user: max_uses_per_user,
77
+ discount_max_uses: max_uses,
78
+ discount_used_count: used_count,
79
+ discount_product_ids: applied_for === "ALL" ? [] : product_ids,
80
+ discount_shop_id: shop_id,
81
+ });
82
+ return newDiscount;
83
+ }
84
+
85
+ static async updateDiscountCode({ code, bodyUpdate }) {
86
+ let body = updateNestedObjectParser(bodyUpdate);
87
+ body = removeBadValue(body);
88
+ return await updateDiscountByCode({
89
+ code,
90
+ body,
91
+ });
92
+ }
93
+
94
+ static async getAllDiscountCodeByShop({ limit, page, shop_id }) {
95
+ return findAllDiscountCodeUnselect({
96
+ filter: {
97
+ discount_shop_id: convertToObjectId(shop_id),
98
+ discount_is_active: true,
99
+ },
100
+ limit: limit,
101
+ page: +page,
102
+ model: discountModel,
103
+ unSelect: ["__v"],
104
+ });
105
+ }
106
+
107
+ static async getProductsByDiscount({ code, shop_id, user_id, limit, page }) {
108
+ const foundDiscount = await discountModel
109
+ .findOne({
110
+ discount_code: code,
111
+ discount_shop_id: convertToObjectId(shop_id),
112
+ })
113
+ .lean();
114
+
115
+ if (!foundDiscount || !foundDiscount.discount_is_active) {
116
+ throw new NotFoundError("Discount does not exist or has expired");
117
+ }
118
+ // applied_for === "ALL" or "SPECIFIC"
119
+ const { discount_applied_for, discount_product_ids } = foundDiscount;
120
+ let products;
121
+ if (discount_applied_for === "ALL") {
122
+ // get all products
123
+ products = await findAllProducts({
124
+ filter: {
125
+ product_shop: convertToObjectId(shop_id),
126
+ isPublished: true,
127
+ },
128
+ limit,
129
+ sort: "ctime",
130
+ page,
131
+ select: ["product_name", "product_price", "product_thumb"],
132
+ });
133
+ } else if (discount_applied_for === "SPECIFIC") {
134
+ products = await findAllProducts({
135
+ filter: {
136
+ _id: { $in: discount_product_ids },
137
+ product_shop: convertToObjectId(shop_id),
138
+ isPublished: true,
139
+ },
140
+ limit,
141
+ sort: "ctime",
142
+ page,
143
+ select: ["product_name", "product_price", "product_thumb"],
144
+ });
145
+ }
146
+ return products;
147
+ }
148
+
149
+ /*
150
+ products = [
151
+ {
152
+ productId,
153
+ shopId,
154
+ quantity,
155
+ name,
156
+ price
157
+ }
158
+ ]
159
+ */
160
+ static async getDiscountAmount({ code, userId, shopId, products }) {
161
+ // ham nay dung de tinh so tien giam sau khi ap dung discount cho danh sach san pham
162
+ const foundDisount = await checkDiscountExists({
163
+ model: discountModel,
164
+ filter: {
165
+ discount_code: code,
166
+ discount_shop_id: convertToObjectId(shopId),
167
+ },
168
+ });
169
+ if (!foundDisount) {
170
+ throw new NotFoundError("Discount does not exist");
171
+ }
172
+
173
+ const {
174
+ discount_is_active,
175
+ discount_max_uses,
176
+ discount_used_count,
177
+ discount_end_date,
178
+ discount_start_date,
179
+ discount_min_order_value,
180
+ discount_max_uses_per_user,
181
+ discount_users_used,
182
+ discount_type,
183
+ discount_value,
184
+ } = foundDisount;
185
+ if (!discount_is_active) {
186
+ throw new NotFoundError("Discount expired");
187
+ }
188
+ if (!discount_max_uses) {
189
+ throw new NotFoundError("Discount is out");
190
+ }
191
+ if (discount_used_count > discount_max_uses) {
192
+ throw new NotFoundError("Discount is out");
193
+ }
194
+ if (
195
+ new Date() < new Date(discount_start_date) ||
196
+ new Date() > new Date(discount_end_date)
197
+ ) {
198
+ throw new NotFoundError("Discount expired");
199
+ }
200
+
201
+ // Neu tong gia tri that don hang < tong don hang toi thieu
202
+ let total_oder = 0;
203
+ if (discount_min_order_value > 0) {
204
+ total_oder = products.reduce((total, product) => {
205
+ return total + product.quantity * product.price;
206
+ }, 0);
207
+ if (total_oder < discount_min_order_value) {
208
+ throw new NotFoundError(
209
+ `Discount requires a min order value of ${discount_min_order_value}`
210
+ );
211
+ }
212
+ }
213
+
214
+ // neu user da su dung ma discount, kiem tra so lan da su dung
215
+ if (discount_max_uses_per_user > 0) {
216
+ const userUsed = discount_users_used.find((id) => id === userId);
217
+ if (userUsed) {
218
+ const userUsedCount = discount_users_used.reduce((total, item) => {
219
+ return item === userId ? total + 1 : total;
220
+ }, 0);
221
+ if (userUsedCount >= discount_max_uses_per_user) {
222
+ throw new BadRequestError("User overused discount");
223
+ }
224
+ }
225
+ }
226
+
227
+ // kiem tra type cua discount de tinh so tien giam
228
+ const discount_amount =
229
+ discount_type === "FIXED_AMOUNT"
230
+ ? discount_value
231
+ : total_oder * (discount_value / 100);
232
+
233
+ return {
234
+ total_oder,
235
+ discount_amount,
236
+ total_price: total_oder - discount_amount,
237
+ };
238
+ }
239
+
240
+ static async deleteDiscount({ code, shopId }) {
241
+ // xóa discount => thực tế phải kiểm tra xem discount này có đơn hàng nào đang sử dụng không
242
+ // phải đảm bảo không còn đơn hàng nào sử dụng thì mới xóa
243
+ // xóa => đưa vào một database mới thay vì thêm trường isDeleted (tốn index)
244
+ const deleted = await discountModel.findOneAndDelete({
245
+ discount_code: code,
246
+ discount_shop_id: convertToObjectId(shopId),
247
+ });
248
+ return deleted;
249
+ }
250
+
251
+ static async cancelDiscount({ code, shopId, userId }) {
252
+ const foundDiscount = await checkDiscountExists({
253
+ model: discountModel,
254
+ filter: {
255
+ discount_code: code,
256
+ discount_shop_id: convertToObjectId(shopId),
257
+ },
258
+ });
259
+
260
+ if (!foundDiscount) {
261
+ throw new NotFoundError("Discount does not exist");
262
+ }
263
+
264
+ const { discount_is_active } = foundDiscount;
265
+ if (!discount_is_active) {
266
+ throw new BadRequestError("Discount expired");
267
+ }
268
+
269
+ const result = await discountModel.findByIdAndUpdate(foundDiscount._id, {
270
+ $pull: {
271
+ discount_users_used: userId,
272
+ },
273
+ $inc: {
274
+ discount_used_count: -1,
275
+ },
276
+ });
277
+
278
+ return result;
279
+ }
280
+ }
281
+
282
+ module.exports = DiscountService;