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.
- package/bin/create.mjs +244 -0
- package/infra/email/files/src/core/config/email.env.ts +73 -0
- package/infra/email/files/src/core/infrastructure/email/email-dispatcher.port.ts +13 -0
- package/infra/email/files/src/core/infrastructure/email/email-layout.spec.ts +83 -0
- package/infra/email/files/src/core/infrastructure/email/email-layout.ts +88 -0
- package/infra/email/files/src/core/infrastructure/email/email-sender.factory.spec.ts +87 -0
- package/infra/email/files/src/core/infrastructure/email/email-sender.factory.ts +35 -0
- package/infra/email/files/src/core/infrastructure/email/email-sender.port.ts +19 -0
- package/infra/email/files/src/core/infrastructure/email/email.job.spec.ts +101 -0
- package/infra/email/files/src/core/infrastructure/email/email.job.ts +64 -0
- package/infra/email/files/src/core/infrastructure/email/email.module.spec.ts +60 -0
- package/infra/email/files/src/core/infrastructure/email/email.module.ts +34 -0
- package/infra/email/files/src/core/infrastructure/email/log-email-sender.spec.ts +34 -0
- package/infra/email/files/src/core/infrastructure/email/log-email-sender.ts +17 -0
- package/infra/email/files/src/core/infrastructure/email/smtp-email-sender.spec.ts +99 -0
- package/infra/email/files/src/core/infrastructure/email/smtp-email-sender.ts +63 -0
- package/infra/email/infra.json +58 -0
- package/infra/prisma-postgres/docker.service.yml +12 -0
- package/infra/prisma-postgres/files/prisma/schema.prisma +10 -0
- package/infra/prisma-postgres/files/prisma.config.mjs +10 -0
- package/infra/prisma-postgres/files/src/core/config/database.env.ts +13 -0
- package/infra/prisma-postgres/files/src/core/infrastructure/database/database.module.ts +9 -0
- package/infra/prisma-postgres/files/src/core/infrastructure/database/prisma.service.ts +26 -0
- package/infra/prisma-postgres/infra.json +60 -0
- package/infra/rabbitmq/docker.service.yml +16 -0
- package/infra/rabbitmq/files/src/core/config/messaging.env.ts +29 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/inline-job-queue.spec.ts +60 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/inline-job-queue.ts +33 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-failure.spec.ts +23 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-failure.ts +19 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-payload-cipher.spec.ts +80 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-payload-cipher.ts +66 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.boundary.spec.ts +34 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.factory.ts +21 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.module.spec.ts +75 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.module.ts +30 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.port.ts +59 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/rabbitmq-job-queue.spec.ts +510 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/rabbitmq-job-queue.ts +353 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/rabbitmq/rabbitmq.module.ts +9 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/rabbitmq/rabbitmq.service.spec.ts +289 -0
- package/infra/rabbitmq/files/src/core/infrastructure/messaging/rabbitmq/rabbitmq.service.ts +207 -0
- package/infra/rabbitmq/infra.json +55 -0
- package/infra/redis/docker.service.yml +10 -0
- package/infra/redis/files/src/core/http/decorators/rate-limit.decorator.ts +26 -0
- package/infra/redis/files/src/core/http/interceptors/rate-limit.interceptor.spec.ts +180 -0
- package/infra/redis/files/src/core/http/interceptors/rate-limit.interceptor.ts +105 -0
- package/infra/redis/files/src/core/infrastructure/redis/cache.constants.ts +67 -0
- package/infra/redis/files/src/core/infrastructure/redis/idempotency/idempotency.module.ts +9 -0
- package/infra/redis/files/src/core/infrastructure/redis/idempotency/idempotency.service.spec.ts +146 -0
- package/infra/redis/files/src/core/infrastructure/redis/idempotency/idempotency.service.ts +131 -0
- package/infra/redis/files/src/core/infrastructure/redis/lock/lock.module.ts +9 -0
- package/infra/redis/files/src/core/infrastructure/redis/lock/lock.service.integration-spec.ts +111 -0
- package/infra/redis/files/src/core/infrastructure/redis/lock/lock.service.ts +82 -0
- package/infra/redis/files/src/core/infrastructure/redis/rate-limit/rate-limit.module.ts +15 -0
- package/infra/redis/files/src/core/infrastructure/redis/rate-limit/rate-limit.service.spec.ts +45 -0
- package/infra/redis/files/src/core/infrastructure/redis/rate-limit/rate-limit.service.ts +38 -0
- package/infra/redis/files/src/core/infrastructure/redis/redis.module.ts +9 -0
- package/infra/redis/files/src/core/infrastructure/redis/redis.service.ts +196 -0
- package/infra/redis/infra.json +53 -0
- package/infra/storage-cloudinary/files/src/core/config/storage.env.ts +43 -0
- package/infra/storage-cloudinary/files/src/core/infrastructure/storage/cloudinary-image-storage.spec.ts +275 -0
- package/infra/storage-cloudinary/files/src/core/infrastructure/storage/cloudinary-image-storage.ts +158 -0
- package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-storage.factory.ts +30 -0
- package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-storage.module.ts +17 -0
- package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-storage.port.ts +38 -0
- package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-type.spec.ts +48 -0
- package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-type.ts +25 -0
- package/infra/storage-cloudinary/files/src/core/infrastructure/storage/unconfigured-image-storage.ts +19 -0
- package/infra/storage-cloudinary/infra.json +73 -0
- package/lib/fs-utils.mjs +115 -0
- package/lib/registry.mjs +50 -0
- package/lib/scaffold.mjs +157 -0
- package/package.json +37 -0
- package/templates/nest-ddd/files/.env.example +5 -0
- package/templates/nest-ddd/files/.husky/commit-msg +1 -0
- package/templates/nest-ddd/files/.husky/pre-commit +1 -0
- package/templates/nest-ddd/files/.oxlintrc.json +10 -0
- package/templates/nest-ddd/files/.prettierrc +4 -0
- package/templates/nest-ddd/files/_gitignore +40 -0
- package/templates/nest-ddd/files/commitlint.config.js +3 -0
- package/templates/nest-ddd/files/nest-cli.json +8 -0
- package/templates/nest-ddd/files/package.json +66 -0
- package/templates/nest-ddd/files/plop-templates/command/command.ts.hbs +5 -0
- package/templates/nest-ddd/files/plop-templates/command/handler.ts.hbs +30 -0
- package/templates/nest-ddd/files/plop-templates/module/application/commands/create-{{kebab}}.command.ts.hbs +5 -0
- package/templates/nest-ddd/files/plop-templates/module/application/commands/create-{{kebab}}.handler.spec.ts.hbs +35 -0
- package/templates/nest-ddd/files/plop-templates/module/application/commands/create-{{kebab}}.handler.ts.hbs +25 -0
- package/templates/nest-ddd/files/plop-templates/module/application/index.ts.hbs +5 -0
- package/templates/nest-ddd/files/plop-templates/module/application/queries/get-{{kebab}}-by-id.handler.ts.hbs +29 -0
- package/templates/nest-ddd/files/plop-templates/module/application/queries/get-{{kebab}}-by-id.query.ts.hbs +5 -0
- package/templates/nest-ddd/files/plop-templates/module/domain/entities/{{kebab}}.entity.ts.hbs +37 -0
- package/templates/nest-ddd/files/plop-templates/module/domain/events/{{kebab}}-created.event.ts.hbs +10 -0
- package/templates/nest-ddd/files/plop-templates/module/domain/exceptions/{{kebab}}.exception.ts.hbs +11 -0
- package/templates/nest-ddd/files/plop-templates/module/domain/index.ts.hbs +4 -0
- package/templates/nest-ddd/files/plop-templates/module/domain/repositories/{{kebab}}.repository.ts.hbs +7 -0
- package/templates/nest-ddd/files/plop-templates/module/presentation/controllers/{{kebab}}.controller.ts.hbs +53 -0
- package/templates/nest-ddd/files/plop-templates/module/presentation/dtos/create-{{kebab}}.dto.ts.hbs +10 -0
- package/templates/nest-ddd/files/plop-templates/module/presentation/dtos/{{kebab}}-response.dto.ts.hbs +15 -0
- package/templates/nest-ddd/files/plop-templates/module/presentation/index.ts.hbs +4 -0
- package/templates/nest-ddd/files/plop-templates/module/presentation/mappers/{{kebab}}.mapper.ts.hbs +13 -0
- package/templates/nest-ddd/files/plop-templates/module/{{kebab}}.module.ts.hbs +40 -0
- package/templates/nest-ddd/files/plop-templates/persistence-memory/infrastructure/database/repositories/in-memory-{{kebab}}.repository.ts.hbs +17 -0
- package/templates/nest-ddd/files/plop-templates/persistence-memory/infrastructure/index.ts.hbs +1 -0
- package/templates/nest-ddd/files/plop-templates/persistence-prisma/infrastructure/database/mappers/{{kebab}}.mapper.ts.hbs +29 -0
- package/templates/nest-ddd/files/plop-templates/persistence-prisma/infrastructure/database/repositories/prisma-{{kebab}}.repository.ts.hbs +28 -0
- package/templates/nest-ddd/files/plop-templates/persistence-prisma/infrastructure/index.ts.hbs +2 -0
- package/templates/nest-ddd/files/plop-templates/query/handler.ts.hbs +28 -0
- package/templates/nest-ddd/files/plop-templates/query/query.ts.hbs +5 -0
- package/templates/nest-ddd/files/plop-templates/snippets/error-codes.hbs +14 -0
- package/templates/nest-ddd/files/plop-templates/snippets/prisma-model.hbs +7 -0
- package/templates/nest-ddd/files/plopfile.mjs +186 -0
- package/templates/nest-ddd/files/src/app.controller.ts +13 -0
- package/templates/nest-ddd/files/src/app.module.ts +26 -0
- package/templates/nest-ddd/files/src/core/config/app.config.ts +9 -0
- package/templates/nest-ddd/files/src/core/config/env.validation.ts +30 -0
- package/templates/nest-ddd/files/src/core/domain/base/aggregate-root.base.ts +18 -0
- package/templates/nest-ddd/files/src/core/domain/base/domain-event.base.ts +7 -0
- package/templates/nest-ddd/files/src/core/domain/base/entity.base.ts +36 -0
- package/templates/nest-ddd/files/src/core/domain/base/value-object.base.ts +17 -0
- package/templates/nest-ddd/files/src/core/domain/exceptions/domain.exception.ts +16 -0
- package/templates/nest-ddd/files/src/core/domain/exceptions/error-codes.ts +28 -0
- package/templates/nest-ddd/files/src/core/domain/types/pagination.ts +9 -0
- package/templates/nest-ddd/files/src/core/http/decorators/client-info.decorator.ts +24 -0
- package/templates/nest-ddd/files/src/core/http/decorators/current-user.decorator.ts +19 -0
- package/templates/nest-ddd/files/src/core/http/dtos/paginated-response.dto.ts +29 -0
- package/templates/nest-ddd/files/src/core/http/dtos/pagination-query.dto.ts +17 -0
- package/templates/nest-ddd/files/src/core/http/filters/all-exceptions.filter.spec.ts +163 -0
- package/templates/nest-ddd/files/src/core/http/filters/all-exceptions.filter.ts +97 -0
- package/templates/nest-ddd/files/src/core/http/guards/max-body-size.guard.spec.ts +47 -0
- package/templates/nest-ddd/files/src/core/http/guards/max-body-size.guard.ts +31 -0
- package/templates/nest-ddd/files/src/core/http/interceptors/logging.interceptor.ts +37 -0
- package/templates/nest-ddd/files/src/core/http/interceptors/response.interceptor.ts +53 -0
- package/templates/nest-ddd/files/src/core/http/logging/app.logger.ts +34 -0
- package/templates/nest-ddd/files/src/core/index.ts +33 -0
- package/templates/nest-ddd/files/src/core/utils/correlation-id.util.spec.ts +29 -0
- package/templates/nest-ddd/files/src/core/utils/correlation-id.util.ts +15 -0
- package/templates/nest-ddd/files/src/core/utils/hash.util.spec.ts +61 -0
- package/templates/nest-ddd/files/src/core/utils/hash.util.ts +47 -0
- package/templates/nest-ddd/files/src/core/utils/snowflake.util.spec.ts +40 -0
- package/templates/nest-ddd/files/src/core/utils/snowflake.util.ts +99 -0
- package/templates/nest-ddd/files/src/core/utils/string.util.spec.ts +20 -0
- package/templates/nest-ddd/files/src/core/utils/string.util.ts +21 -0
- package/templates/nest-ddd/files/src/core/utils/time.util.spec.ts +25 -0
- package/templates/nest-ddd/files/src/core/utils/time.util.ts +25 -0
- package/templates/nest-ddd/files/src/core/utils/ttl.util.ts +22 -0
- package/templates/nest-ddd/files/src/main.ts +61 -0
- package/templates/nest-ddd/files/test/setup.ts +6 -0
- package/templates/nest-ddd/files/tsconfig.build.json +9 -0
- package/templates/nest-ddd/files/tsconfig.json +28 -0
- package/templates/nest-ddd/files/vitest.config.e2e.ts +15 -0
- package/templates/nest-ddd/files/vitest.config.ts +16 -0
- package/templates/nest-ddd/template.json +8 -0
- package/templates/node-express/files/.env.example +28 -0
- package/templates/node-express/files/docker-compose.yml +52 -0
- package/templates/node-express/files/package-lock.json +11880 -0
- package/templates/node-express/files/package.json +43 -0
- package/templates/node-express/files/server.js +13 -0
- package/templates/node-express/files/src/app.js +72 -0
- package/templates/node-express/files/src/auth/authUtils.js +116 -0
- package/templates/node-express/files/src/auth/checkAuth.js +61 -0
- package/templates/node-express/files/src/bucket_pattern/comment.model.js +27 -0
- package/templates/node-express/files/src/bucket_pattern/comment.service.js +50 -0
- package/templates/node-express/files/src/config/config.aws-s3.js +28 -0
- package/templates/node-express/files/src/config/config.cloudinary.js +13 -0
- package/templates/node-express/files/src/config/config.mongodb.js +25 -0
- package/templates/node-express/files/src/config/config.multer.js +25 -0
- package/templates/node-express/files/src/config/config.nodemailer.js +16 -0
- package/templates/node-express/files/src/controllers/access.controller.js +49 -0
- package/templates/node-express/files/src/controllers/cart.controller.js +38 -0
- package/templates/node-express/files/src/controllers/checkout.controller.js +15 -0
- package/templates/node-express/files/src/controllers/comment.controller.js +29 -0
- package/templates/node-express/files/src/controllers/discount.controller.js +68 -0
- package/templates/node-express/files/src/controllers/email.controller.js +16 -0
- package/templates/node-express/files/src/controllers/inventory.controller.js +15 -0
- package/templates/node-express/files/src/controllers/notification.controller.js +15 -0
- package/templates/node-express/files/src/controllers/product.controller.js +175 -0
- package/templates/node-express/files/src/controllers/profile.controller.js +65 -0
- package/templates/node-express/files/src/controllers/rbac.controller.js +41 -0
- package/templates/node-express/files/src/controllers/template.controller.js +20 -0
- package/templates/node-express/files/src/controllers/upload.controller.js +56 -0
- package/templates/node-express/files/src/controllers/user.controller.js +36 -0
- package/templates/node-express/files/src/core/error.response.js +84 -0
- package/templates/node-express/files/src/core/success.response.js +44 -0
- package/templates/node-express/files/src/database/init.mongodb.level0.js +16 -0
- package/templates/node-express/files/src/database/init.mongodb.level1.js +36 -0
- package/templates/node-express/files/src/database/init.redis.js +119 -0
- package/templates/node-express/files/src/helpers/asyncHandler.js +7 -0
- package/templates/node-express/files/src/helpers/check.connect.js +29 -0
- package/templates/node-express/files/src/keys/private_key.pem +28 -0
- package/templates/node-express/files/src/keys/public_key.pem +9 -0
- package/templates/node-express/files/src/logger/discord.log.js +26 -0
- package/templates/node-express/files/src/logger/discord.log.v2.js +56 -0
- package/templates/node-express/files/src/logger/mylogger.log.js +130 -0
- package/templates/node-express/files/src/logger/winston.log.js +27 -0
- package/templates/node-express/files/src/middlewares/index.js +21 -0
- package/templates/node-express/files/src/middlewares/rbac.js +37 -0
- package/templates/node-express/files/src/middlewares/role.middleware.js +12 -0
- package/templates/node-express/files/src/models/KeyToken.model.js +37 -0
- package/templates/node-express/files/src/models/apiKey.model.js +31 -0
- package/templates/node-express/files/src/models/cart.model.js +47 -0
- package/templates/node-express/files/src/models/comment.model.js +43 -0
- package/templates/node-express/files/src/models/discount.model.js +92 -0
- package/templates/node-express/files/src/models/inventory.model.js +42 -0
- package/templates/node-express/files/src/models/notification.model.js +52 -0
- package/templates/node-express/files/src/models/order.model.js +68 -0
- package/templates/node-express/files/src/models/otp.model.js +32 -0
- package/templates/node-express/files/src/models/product.model.js +144 -0
- package/templates/node-express/files/src/models/repository/cart.repo.js +71 -0
- package/templates/node-express/files/src/models/repository/discount.repo.js +63 -0
- package/templates/node-express/files/src/models/repository/inventory.repo.js +45 -0
- package/templates/node-express/files/src/models/repository/product.repo.js +145 -0
- package/templates/node-express/files/src/models/repository/shop.repo.js +18 -0
- package/templates/node-express/files/src/models/repository/spu.repo.js +18 -0
- package/templates/node-express/files/src/models/repository/user.repo.js +20 -0
- package/templates/node-express/files/src/models/resource.model.js +32 -0
- package/templates/node-express/files/src/models/role.model.js +57 -0
- package/templates/node-express/files/src/models/shop.model.js +43 -0
- package/templates/node-express/files/src/models/sku.model.js +81 -0
- package/templates/node-express/files/src/models/spu.model.js +101 -0
- package/templates/node-express/files/src/models/template.model.js +25 -0
- package/templates/node-express/files/src/models/user.model.js +82 -0
- package/templates/node-express/files/src/postman/access.post.http +22 -0
- package/templates/node-express/files/src/routes/access/index.route.js +21 -0
- package/templates/node-express/files/src/routes/cart/index.route.js +20 -0
- package/templates/node-express/files/src/routes/checkout/index.route.js +10 -0
- package/templates/node-express/files/src/routes/comment/index.route.js +16 -0
- package/templates/node-express/files/src/routes/discount/index.route.js +26 -0
- package/templates/node-express/files/src/routes/index.route.js +27 -0
- package/templates/node-express/files/src/routes/inventory/index.route.js +11 -0
- package/templates/node-express/files/src/routes/notification/index.route.js +14 -0
- package/templates/node-express/files/src/routes/product/index.route.js +38 -0
- package/templates/node-express/files/src/routes/profile/index.route.js +15 -0
- package/templates/node-express/files/src/routes/rbac/index.route.js +21 -0
- package/templates/node-express/files/src/routes/template/index.route.js +11 -0
- package/templates/node-express/files/src/routes/upload/index.route.js +28 -0
- package/templates/node-express/files/src/routes/user/index.route.js +13 -0
- package/templates/node-express/files/src/services/access.service.js +257 -0
- package/templates/node-express/files/src/services/apiKey.service.js +15 -0
- package/templates/node-express/files/src/services/cart.service.js +115 -0
- package/templates/node-express/files/src/services/checkout.service.js +218 -0
- package/templates/node-express/files/src/services/comment.service.js +185 -0
- package/templates/node-express/files/src/services/discount.service.js +282 -0
- package/templates/node-express/files/src/services/email.service.js +71 -0
- package/templates/node-express/files/src/services/inventory.service.js +35 -0
- package/templates/node-express/files/src/services/keyToken.service.js +77 -0
- package/templates/node-express/files/src/services/notification.service.js +58 -0
- package/templates/node-express/files/src/services/otp.service.js +36 -0
- package/templates/node-express/files/src/services/product.service.js +228 -0
- package/templates/node-express/files/src/services/rbac.service.js +143 -0
- package/templates/node-express/files/src/services/redis.service.js +57 -0
- package/templates/node-express/files/src/services/redisPubSub.service.js +72 -0
- package/templates/node-express/files/src/services/shop.service.js +21 -0
- package/templates/node-express/files/src/services/sku.service.js +52 -0
- package/templates/node-express/files/src/services/spu.service.js +89 -0
- package/templates/node-express/files/src/services/template.service.js +41 -0
- package/templates/node-express/files/src/services/upload.s3.service.js +55 -0
- package/templates/node-express/files/src/services/upload.service.js +74 -0
- package/templates/node-express/files/src/services/user.service.js +92 -0
- package/templates/node-express/files/src/temp/1734420604996-ci-cd.png +0 -0
- package/templates/node-express/files/src/temp/1734421222213-cover-fb.jpg +0 -0
- package/templates/node-express/files/src/temp/1734421222214-cover-fb-01.jpg +0 -0
- package/templates/node-express/files/src/temp/1734421293437-cover-fb-01.jpg +0 -0
- package/templates/node-express/files/src/temp/1734421293437-cover-fb.jpg +0 -0
- package/templates/node-express/files/src/tests/inventory.test.js +21 -0
- package/templates/node-express/files/src/tests/message_queue/kafka/consumer.js +23 -0
- package/templates/node-express/files/src/tests/message_queue/kafka/producer.js +21 -0
- package/templates/node-express/files/src/tests/message_queue/rabbitMQ/consumer.js +28 -0
- package/templates/node-express/files/src/tests/message_queue/rabbitMQ/ordered.consumer.js +26 -0
- package/templates/node-express/files/src/tests/message_queue/rabbitMQ/ordered.producer.js +31 -0
- package/templates/node-express/files/src/tests/message_queue/rabbitMQ/producer.js +26 -0
- package/templates/node-express/files/src/tests/message_queue/rabbitMQ/producerDLX.js +44 -0
- package/templates/node-express/files/src/tests/mysql/connect.js +59 -0
- package/templates/node-express/files/src/tests/product.test.js +14 -0
- package/templates/node-express/files/src/utils/index.js +78 -0
- package/templates/node-express/files/src/utils/reasonPhrases.js +344 -0
- package/templates/node-express/files/src/utils/statusCodes.js +344 -0
- package/templates/node-express/files/src/utils/template.html.js +38 -0
- package/templates/node-express/template.json +11 -0
- package/templates/react-fsd/files/index.html +13 -0
- package/templates/react-fsd/files/package.json +15 -0
- package/templates/react-fsd/files/public/favicon.svg +1 -0
- package/templates/react-fsd/files/public/icons.svg +24 -0
- package/templates/react-fsd/files/src/assets/hero.png +0 -0
- package/templates/react-fsd/files/src/assets/typescript.svg +1 -0
- package/templates/react-fsd/files/src/assets/vite.svg +1 -0
- package/templates/react-fsd/files/src/counter.ts +9 -0
- package/templates/react-fsd/files/src/main.ts +60 -0
- package/templates/react-fsd/files/src/style.css +296 -0
- package/templates/react-fsd/files/tsconfig.json +24 -0
- package/templates/react-fsd/template.json +11 -0
- package/templates/react-monolithic/files/index.html +13 -0
- package/templates/react-monolithic/files/package.json +15 -0
- package/templates/react-monolithic/files/public/favicon.svg +1 -0
- package/templates/react-monolithic/files/public/icons.svg +24 -0
- package/templates/react-monolithic/files/src/assets/hero.png +0 -0
- package/templates/react-monolithic/files/src/assets/typescript.svg +1 -0
- package/templates/react-monolithic/files/src/assets/vite.svg +1 -0
- package/templates/react-monolithic/files/src/counter.ts +9 -0
- package/templates/react-monolithic/files/src/main.ts +60 -0
- package/templates/react-monolithic/files/src/style.css +296 -0
- package/templates/react-monolithic/files/tsconfig.json +24 -0
- package/templates/react-monolithic/template.json +11 -0
- package/templates/vue-fsd/files/index.html +13 -0
- package/templates/vue-fsd/files/package.json +15 -0
- package/templates/vue-fsd/files/public/favicon.svg +1 -0
- package/templates/vue-fsd/files/public/icons.svg +24 -0
- package/templates/vue-fsd/files/src/assets/hero.png +0 -0
- package/templates/vue-fsd/files/src/assets/typescript.svg +1 -0
- package/templates/vue-fsd/files/src/assets/vite.svg +1 -0
- package/templates/vue-fsd/files/src/counter.ts +9 -0
- package/templates/vue-fsd/files/src/main.ts +60 -0
- package/templates/vue-fsd/files/src/style.css +296 -0
- package/templates/vue-fsd/files/tsconfig.json +24 -0
- package/templates/vue-fsd/template.json +11 -0
- package/templates/vue-monolithic/files/index.html +13 -0
- package/templates/vue-monolithic/files/package.json +15 -0
- package/templates/vue-monolithic/files/public/favicon.svg +1 -0
- package/templates/vue-monolithic/files/public/icons.svg +24 -0
- package/templates/vue-monolithic/files/src/assets/hero.png +0 -0
- package/templates/vue-monolithic/files/src/assets/typescript.svg +1 -0
- package/templates/vue-monolithic/files/src/assets/vite.svg +1 -0
- package/templates/vue-monolithic/files/src/counter.ts +9 -0
- package/templates/vue-monolithic/files/src/main.ts +60 -0
- package/templates/vue-monolithic/files/src/style.css +296 -0
- package/templates/vue-monolithic/files/tsconfig.json +24 -0
- 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;
|