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,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const OtpService = require("./otp.service");
|
|
4
|
+
const TemplateService = require("./template.service");
|
|
5
|
+
const transporter = require("../config/config.nodemailer");
|
|
6
|
+
const { replacePlaceholder } = require("../utils/template.html");
|
|
7
|
+
const { NotFoundError, BadRequestError } = require("../core/error.response");
|
|
8
|
+
|
|
9
|
+
class EmailService {
|
|
10
|
+
static sendEmailUsingThirdParty({
|
|
11
|
+
toEmail = null,
|
|
12
|
+
html = null,
|
|
13
|
+
subject = "Xác thực tài khoản email của bạn",
|
|
14
|
+
}) {
|
|
15
|
+
try {
|
|
16
|
+
// send email using nodemailer
|
|
17
|
+
transporter.sendMail(
|
|
18
|
+
{
|
|
19
|
+
from: "ShopDEV <thuyphuocthinhtpt1@gmail.com>",
|
|
20
|
+
to: toEmail,
|
|
21
|
+
subject,
|
|
22
|
+
html,
|
|
23
|
+
},
|
|
24
|
+
(error, info) => {
|
|
25
|
+
if (error) {
|
|
26
|
+
throw new BadRequestError("Failed to send email");
|
|
27
|
+
}
|
|
28
|
+
console.log("Message sent: %s", info.messageId);
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new BadRequestError("Failed to send email");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static async sendEmail({ email = null }) {
|
|
37
|
+
try {
|
|
38
|
+
// get token from otp
|
|
39
|
+
const otp = await OtpService.newOtp({ email });
|
|
40
|
+
|
|
41
|
+
// get template
|
|
42
|
+
const template = await TemplateService.getTemplate({
|
|
43
|
+
template_name: "HTML_EMAIL_TOKEN",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!template) {
|
|
47
|
+
throw new NotFoundError("Template not found");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// replace placeholder
|
|
51
|
+
const html = replacePlaceholder(template.template_html, {
|
|
52
|
+
verify_link: `http://localhost:3000/api/v1/user/verify-email?email=${email}&token=${otp.otp_token}`,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// send email
|
|
56
|
+
EmailService.sendEmailUsingThirdParty({
|
|
57
|
+
toEmail: email,
|
|
58
|
+
html: html,
|
|
59
|
+
subject: "Xác thực tài khoản email của bạn",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
message: "Email sent successfully, please check your email",
|
|
64
|
+
};
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new BadRequestError("Failed to send email");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = EmailService;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { NotFoundError } = require("../core/error.response");
|
|
4
|
+
const inventoryModel = require("../models/inventory.model");
|
|
5
|
+
const { getProductById } = require("../models/repository/product.repo");
|
|
6
|
+
|
|
7
|
+
class InventoryService {
|
|
8
|
+
static async addStockToInventory({
|
|
9
|
+
stock,
|
|
10
|
+
productId,
|
|
11
|
+
shopId,
|
|
12
|
+
location = "14 Doan Uan, Danang city",
|
|
13
|
+
}) {
|
|
14
|
+
const product = await getProductById({ productId });
|
|
15
|
+
if (!product) {
|
|
16
|
+
throw new NotFoundError("Product does not exist");
|
|
17
|
+
}
|
|
18
|
+
const query = {
|
|
19
|
+
inventory_shop_id: shopId,
|
|
20
|
+
inventory_product_id: productId,
|
|
21
|
+
};
|
|
22
|
+
const updateSet = {
|
|
23
|
+
$inc: {
|
|
24
|
+
inventory_stock: +stock,
|
|
25
|
+
},
|
|
26
|
+
$set: {
|
|
27
|
+
inventory_location: location,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const options = { upsert: true, new: true };
|
|
31
|
+
return await inventoryModel.findOneAndUpdate(query, updateSet, options);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = InventoryService;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { Types } = require("mongoose");
|
|
4
|
+
const KeyTokenModel = require("../models/KeyToken.model");
|
|
5
|
+
|
|
6
|
+
class KeyTokenService {
|
|
7
|
+
static createKeyToken = async ({
|
|
8
|
+
userId,
|
|
9
|
+
publicKey,
|
|
10
|
+
privateKey,
|
|
11
|
+
refreshToken,
|
|
12
|
+
}) => {
|
|
13
|
+
try {
|
|
14
|
+
// const tokens = await KeyTokenModel.create({
|
|
15
|
+
// user: userId,
|
|
16
|
+
// publicKey,
|
|
17
|
+
// privateKey,
|
|
18
|
+
// });
|
|
19
|
+
// return tokens ? tokens : null;
|
|
20
|
+
const filter = { user: userId };
|
|
21
|
+
const update = {
|
|
22
|
+
publicKey,
|
|
23
|
+
privateKey,
|
|
24
|
+
refreshTokenUsed: [],
|
|
25
|
+
refreshToken,
|
|
26
|
+
};
|
|
27
|
+
const options = { upsert: true, new: true };
|
|
28
|
+
const tokens = await KeyTokenModel.findOneAndUpdate(
|
|
29
|
+
filter,
|
|
30
|
+
update,
|
|
31
|
+
options
|
|
32
|
+
);
|
|
33
|
+
return tokens ? tokens.publicKey : null;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return error;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
static getKeyPair = async ({ userId }) => {
|
|
40
|
+
const keyPair = await KeyTokenModel.findOne({ user: userId });
|
|
41
|
+
return {
|
|
42
|
+
privateKey: keyPair.privateKey,
|
|
43
|
+
publicKey: keyPair.publicKey,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
static findByUserId = async ({ userId }) => {
|
|
48
|
+
const found = await KeyTokenModel.findOne({
|
|
49
|
+
user: new Types.ObjectId(userId),
|
|
50
|
+
});
|
|
51
|
+
return found;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
static removeKeyTokenById = async (id) => {
|
|
55
|
+
return await KeyTokenModel.deleteOne({ _id: id });
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
static findByRefreshTokenUsed = async (refreshToken) => {
|
|
59
|
+
return await KeyTokenModel.findOne({
|
|
60
|
+
refreshTokenUsed: refreshToken,
|
|
61
|
+
}).lean();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
static findByRefreshToken = async (refreshToken) => {
|
|
65
|
+
return await KeyTokenModel.findOne({
|
|
66
|
+
refreshToken: refreshToken,
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
static deleteKeyByUserId = async (userId) => {
|
|
71
|
+
return await KeyTokenModel.findOneAndDelete({
|
|
72
|
+
user: new Types.ObjectId(userId),
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = KeyTokenService;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const notificationModel = require("../models/notification.model");
|
|
4
|
+
const { convertToObjectId } = require("../utils");
|
|
5
|
+
|
|
6
|
+
const NOTI_TYPE_CONTENT = {
|
|
7
|
+
"SHOP-001": "Vừa mới thêm một sản phẩm",
|
|
8
|
+
"ORDER-001": "Đặt hàng thành công",
|
|
9
|
+
"ORDER-002": "Đặt hàng thất bại",
|
|
10
|
+
"ORDER-003": "Giao hàng thành công",
|
|
11
|
+
"PROMOTION-001": "Vừa mới thêm voucher mới",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
class NotificationService {
|
|
15
|
+
static async pushNotiToSystem({
|
|
16
|
+
type = "SHOP-001",
|
|
17
|
+
receiverId,
|
|
18
|
+
senderId,
|
|
19
|
+
options = {},
|
|
20
|
+
}) {
|
|
21
|
+
let noti_content = NOTI_TYPE_CONTENT[type];
|
|
22
|
+
const newNoti = await notificationModel({
|
|
23
|
+
noti_content,
|
|
24
|
+
noti_type: type,
|
|
25
|
+
noti_receiverId: receiverId,
|
|
26
|
+
noti_senderId: convertToObjectId(senderId),
|
|
27
|
+
noti_options: options,
|
|
28
|
+
});
|
|
29
|
+
return newNoti;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static async listNotiByUser({ userId = 1, type = "ALL", isRead = 0 }) {
|
|
33
|
+
const match = {
|
|
34
|
+
noti_receiverId: userId,
|
|
35
|
+
};
|
|
36
|
+
if (type !== "ALL") {
|
|
37
|
+
match["noti_type"] = type;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return await notificationModel.aggregate([
|
|
41
|
+
{
|
|
42
|
+
$match: match,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
$project: {
|
|
46
|
+
noti_type: 1,
|
|
47
|
+
noti_senderId: 1,
|
|
48
|
+
noti_receiverId: 1,
|
|
49
|
+
noti_content: 1,
|
|
50
|
+
createdAt: 1,
|
|
51
|
+
noti_options: 1,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = NotificationService;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { Otp } = require("../models/otp.model");
|
|
4
|
+
const { BadRequestError } = require("../core/error.response");
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
|
|
7
|
+
class OtpService {
|
|
8
|
+
static async generateToken() {
|
|
9
|
+
return crypto.randomInt(0, Math.pow(2, 32));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static async newOtp({ email = null }) {
|
|
13
|
+
// generate token
|
|
14
|
+
const token = await OtpService.generateToken();
|
|
15
|
+
|
|
16
|
+
// new otp
|
|
17
|
+
const newOtp = await Otp.create({
|
|
18
|
+
otp_email: email,
|
|
19
|
+
otp_token: token,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return newOtp;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static async checkOtp({ email = null, token = null }) {
|
|
26
|
+
const otp = await Otp.findOne({ email, otp_token: token }).lean().exec();
|
|
27
|
+
if (!otp) {
|
|
28
|
+
throw new BadRequestError("Invalid token");
|
|
29
|
+
}
|
|
30
|
+
// delete because we only use one time - and otp is valid
|
|
31
|
+
await Otp.deleteOne({ email, otp_token: token });
|
|
32
|
+
return otp;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = OtpService;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { BadRequestError } = require("../core/error.response");
|
|
4
|
+
const {
|
|
5
|
+
product,
|
|
6
|
+
clothing,
|
|
7
|
+
electronic,
|
|
8
|
+
furniture,
|
|
9
|
+
} = require("../models/product.model");
|
|
10
|
+
const { insertInventory } = require("../models/repository/inventory.repo");
|
|
11
|
+
const {
|
|
12
|
+
findAllDraftProducts,
|
|
13
|
+
publishProductByShop,
|
|
14
|
+
findAllPublishedProducts,
|
|
15
|
+
unpublishProductByShop,
|
|
16
|
+
searchProductsByUser,
|
|
17
|
+
findAllProducts,
|
|
18
|
+
findDetailProduct,
|
|
19
|
+
updateProductById,
|
|
20
|
+
} = require("../models/repository/product.repo");
|
|
21
|
+
const { removeBadValue, updateNestedObjectParser } = require("../utils");
|
|
22
|
+
const NotificationService = require("./notification.service");
|
|
23
|
+
const TYPES = {
|
|
24
|
+
Electronics: "Electronics",
|
|
25
|
+
Clothing: "Clothing",
|
|
26
|
+
Furniture: "Furniture",
|
|
27
|
+
};
|
|
28
|
+
// Define Factory class to create product
|
|
29
|
+
class ProductFactory {
|
|
30
|
+
static productRegistry = {}; // key : class
|
|
31
|
+
|
|
32
|
+
static registerProductType(type, classRef) {
|
|
33
|
+
ProductFactory.productRegistry[type] = classRef;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static async createProduct(type, payload) {
|
|
37
|
+
const productType = ProductFactory.productRegistry[type];
|
|
38
|
+
console.log(">>> productType:::", productType);
|
|
39
|
+
if (!productType)
|
|
40
|
+
throw new BadRequestError(`Invalid Product Type::${type}`);
|
|
41
|
+
return new productType(payload).createProduct();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static async findAllDraftProducts({ product_shop, limit = 50, skip = 0 }) {
|
|
45
|
+
const query = { product_shop, isDraft: true };
|
|
46
|
+
return await findAllDraftProducts({ query, limit, skip });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static async publishProductByShop({ product_shop, product_id }) {
|
|
50
|
+
return await publishProductByShop({ product_shop, product_id });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static async findAllPublishedProducts({
|
|
54
|
+
product_shop,
|
|
55
|
+
limit = 50,
|
|
56
|
+
skip = 0,
|
|
57
|
+
}) {
|
|
58
|
+
const query = { product_shop, isPublished: true };
|
|
59
|
+
return await findAllPublishedProducts({ query, limit, skip });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static async unpublishProductByShop({ product_shop, product_id }) {
|
|
63
|
+
return await unpublishProductByShop({ product_shop, product_id });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static async searchProducts({ keySearch }) {
|
|
67
|
+
return await searchProductsByUser({ keySearch });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static async findAllProducts({
|
|
71
|
+
limit = 50,
|
|
72
|
+
sort = "ctime",
|
|
73
|
+
page = 1,
|
|
74
|
+
filter = { isPublished: true },
|
|
75
|
+
}) {
|
|
76
|
+
return await findAllProducts({
|
|
77
|
+
limit,
|
|
78
|
+
sort,
|
|
79
|
+
page,
|
|
80
|
+
filter,
|
|
81
|
+
select: ["product_name", "product_thumb", "product_price"],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static async findDetailProduct({ product_id }) {
|
|
86
|
+
return await findDetailProduct({ product_id, unSelect: ["__v"] });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
static async updateProduct({ product_type, product_id, bodyUpdate }) {
|
|
90
|
+
const productType = ProductFactory.productRegistry[product_type];
|
|
91
|
+
if (!productType) {
|
|
92
|
+
throw new BadRequestError(`Invalid Product Type::${productType}`);
|
|
93
|
+
}
|
|
94
|
+
console.log(">>> productType:::", productType);
|
|
95
|
+
return new productType(bodyUpdate).updateProduct({
|
|
96
|
+
product_id,
|
|
97
|
+
bodyUpdate,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Define base Product class
|
|
103
|
+
class Product {
|
|
104
|
+
constructor({
|
|
105
|
+
product_name,
|
|
106
|
+
product_thumb,
|
|
107
|
+
product_description,
|
|
108
|
+
product_price,
|
|
109
|
+
product_quantity,
|
|
110
|
+
product_type,
|
|
111
|
+
product_shop,
|
|
112
|
+
product_attributes,
|
|
113
|
+
}) {
|
|
114
|
+
this.product_name = product_name;
|
|
115
|
+
this.product_thumb = product_thumb;
|
|
116
|
+
this.product_description = product_description;
|
|
117
|
+
this.product_price = product_price;
|
|
118
|
+
this.product_quantity = product_quantity;
|
|
119
|
+
this.product_type = product_type;
|
|
120
|
+
this.product_shop = product_shop;
|
|
121
|
+
this.product_attributes = product_attributes;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async createProduct(product_id) {
|
|
125
|
+
const newProduct = await product.create({ ...this, _id: product_id });
|
|
126
|
+
if (newProduct) {
|
|
127
|
+
await insertInventory({
|
|
128
|
+
product_id: newProduct._id,
|
|
129
|
+
shop_id: newProduct.product_shop,
|
|
130
|
+
stock: newProduct.product_quantity,
|
|
131
|
+
location: "Danang, Vietnam",
|
|
132
|
+
});
|
|
133
|
+
// noti new product is created here
|
|
134
|
+
// push noti to noti system
|
|
135
|
+
NotificationService.pushNotiToSystem({
|
|
136
|
+
type: "SHOP-001",
|
|
137
|
+
senderId: this.product_attributes,
|
|
138
|
+
receiverId: 1,
|
|
139
|
+
options: {
|
|
140
|
+
product_name: this.product_name,
|
|
141
|
+
shop_name: this.product_shop,
|
|
142
|
+
},
|
|
143
|
+
})
|
|
144
|
+
.then((res) => console.log(res))
|
|
145
|
+
.catch((err) => console.log(err));
|
|
146
|
+
return newProduct;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async updateProduct({ product_id, bodyUpdate }) {
|
|
151
|
+
return await updateProductById({ product_id, bodyUpdate, model: product });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Define Clothing
|
|
156
|
+
class Clothing extends Product {
|
|
157
|
+
async createProduct() {
|
|
158
|
+
const newClothing = await clothing.create({
|
|
159
|
+
...this.product_attributes,
|
|
160
|
+
product_shop: this.product_shop,
|
|
161
|
+
});
|
|
162
|
+
if (!newClothing) {
|
|
163
|
+
throw new BadRequestError("Create new Clothing error");
|
|
164
|
+
}
|
|
165
|
+
const newProduct = await super.createProduct(newClothing._id);
|
|
166
|
+
if (!newProduct) {
|
|
167
|
+
throw new BadRequestError("Create new Product error");
|
|
168
|
+
}
|
|
169
|
+
return newProduct;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async updateProduct({ product_id, bodyUpdate }) {
|
|
173
|
+
// 1. Remove undefined, null attributes
|
|
174
|
+
let payload = updateNestedObjectParser(bodyUpdate);
|
|
175
|
+
payload = removeBadValue(payload);
|
|
176
|
+
// 2. Check xem update o cho nao
|
|
177
|
+
if (bodyUpdate.product_attributes) {
|
|
178
|
+
// update child
|
|
179
|
+
await updateProductById({ product_id, payload, model: clothing });
|
|
180
|
+
}
|
|
181
|
+
// update parent
|
|
182
|
+
const updateProduct = await super.updateProduct({ product_id, payload });
|
|
183
|
+
return updateProduct;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Define Electronic
|
|
188
|
+
class Electronics extends Product {
|
|
189
|
+
async createProduct() {
|
|
190
|
+
const newElectronics = await electronic.create({
|
|
191
|
+
...this.product_attributes,
|
|
192
|
+
product_shop: this.product_shop,
|
|
193
|
+
});
|
|
194
|
+
if (!newElectronics) {
|
|
195
|
+
throw new BadRequestError("Create new Electronics error");
|
|
196
|
+
}
|
|
197
|
+
const newProduct = await super.createProduct(newElectronics._id);
|
|
198
|
+
if (!newProduct) {
|
|
199
|
+
throw new BadRequestError("Create new Product error");
|
|
200
|
+
}
|
|
201
|
+
return newProduct;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Define Furniture
|
|
206
|
+
class Furniture extends Product {
|
|
207
|
+
async createProduct() {
|
|
208
|
+
const newFurniture = await furniture.create({
|
|
209
|
+
...this.product_attributes,
|
|
210
|
+
product_shop: this.product_shop,
|
|
211
|
+
});
|
|
212
|
+
if (!newFurniture) {
|
|
213
|
+
throw new BadRequestError("Create new Furniture error");
|
|
214
|
+
}
|
|
215
|
+
const newProduct = await super.createProduct(newFurniture._id);
|
|
216
|
+
if (!newProduct) {
|
|
217
|
+
throw new BadRequestError("Create new Product error");
|
|
218
|
+
}
|
|
219
|
+
return newProduct;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Regiter productType
|
|
224
|
+
ProductFactory.registerProductType(TYPES.Clothing, Clothing);
|
|
225
|
+
ProductFactory.registerProductType(TYPES.Electronics, Electronics);
|
|
226
|
+
ProductFactory.registerProductType(TYPES.Furniture, Furniture);
|
|
227
|
+
|
|
228
|
+
module.exports = ProductFactory;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { BadRequestError } = require("../core/error.response");
|
|
4
|
+
const Resource = require("../models/resource.model");
|
|
5
|
+
const Role = require("../models/role.model");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param {string} resource_name
|
|
10
|
+
* @param {string} resource_slug
|
|
11
|
+
* @param {string} resource_description
|
|
12
|
+
* @returns
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const createResource = async ({
|
|
16
|
+
resource_name = "profile",
|
|
17
|
+
resource_slug = "p000001",
|
|
18
|
+
resource_description = "profile description",
|
|
19
|
+
}) => {
|
|
20
|
+
try {
|
|
21
|
+
// 1. Check if resource already exists
|
|
22
|
+
const foundResource = await Resource.findOne({ resource_slug });
|
|
23
|
+
if (foundResource) {
|
|
24
|
+
throw new BadRequestError("Resource already exists");
|
|
25
|
+
}
|
|
26
|
+
// 2. If not, create new resource
|
|
27
|
+
const newResource = await Resource.create({
|
|
28
|
+
resource_name,
|
|
29
|
+
resource_slug,
|
|
30
|
+
resource_description,
|
|
31
|
+
});
|
|
32
|
+
// 3. Return new resource
|
|
33
|
+
return newResource;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw new BadRequestError(error.message);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const resourceList = async ({
|
|
40
|
+
userId = 0,
|
|
41
|
+
limit = 30,
|
|
42
|
+
offset = 0,
|
|
43
|
+
search = "",
|
|
44
|
+
}) => {
|
|
45
|
+
try {
|
|
46
|
+
// 1. get list resource
|
|
47
|
+
const resources = await Resource.aggregate([
|
|
48
|
+
{
|
|
49
|
+
$project: {
|
|
50
|
+
_id: 0,
|
|
51
|
+
resource_name: "$resource_name",
|
|
52
|
+
resource_slug: "$resource_slug",
|
|
53
|
+
resource_description: "$resource_description",
|
|
54
|
+
resourceId: "$_id",
|
|
55
|
+
createdAt: 1,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
]);
|
|
59
|
+
// 3. Return list resource
|
|
60
|
+
return resources;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw new BadRequestError(error.message);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const createRole = async ({
|
|
67
|
+
role_name = "admin",
|
|
68
|
+
role_slug = "r000001",
|
|
69
|
+
role_description = "admin description",
|
|
70
|
+
role_grants = [],
|
|
71
|
+
}) => {
|
|
72
|
+
try {
|
|
73
|
+
// 1. Check if role already exists
|
|
74
|
+
const foundRole = await Role.findOne({ role_slug });
|
|
75
|
+
if (foundRole) {
|
|
76
|
+
throw new BadRequestError("Role already exists");
|
|
77
|
+
}
|
|
78
|
+
// 2. If not, create new role
|
|
79
|
+
const newRole = await Role.create({
|
|
80
|
+
role_name,
|
|
81
|
+
role_slug,
|
|
82
|
+
role_description,
|
|
83
|
+
role_grants,
|
|
84
|
+
});
|
|
85
|
+
// 3. Return new role
|
|
86
|
+
return newRole;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
throw new BadRequestError(error.message);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const roleList = async ({
|
|
93
|
+
userId = 0,
|
|
94
|
+
limit = 30,
|
|
95
|
+
offset = 0,
|
|
96
|
+
search = "",
|
|
97
|
+
}) => {
|
|
98
|
+
try {
|
|
99
|
+
// list roles
|
|
100
|
+
const roles = await Role.aggregate(
|
|
101
|
+
{
|
|
102
|
+
$unwind: "$role_grants",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
$lookup: {
|
|
106
|
+
from: "resources",
|
|
107
|
+
localField: "role_grants.resource_id",
|
|
108
|
+
foreignField: "_id",
|
|
109
|
+
as: "resource",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
$unwind: "$resource",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
$project: {
|
|
117
|
+
role: "$role_name",
|
|
118
|
+
resource: "$resource.resource_name",
|
|
119
|
+
actions: "$role_grants.actions",
|
|
120
|
+
attributes: "$role_grants.attributes",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
$unwind: "$actions",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
$project: {
|
|
128
|
+
_id: 0,
|
|
129
|
+
role: 1,
|
|
130
|
+
resource: 1,
|
|
131
|
+
actions: "$actions",
|
|
132
|
+
attributes: 1,
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return roles;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
throw new BadRequestError(error.message);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
module.exports = { createResource, resourceList, createRole, roleList };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const redis = require("redis");
|
|
4
|
+
const { promisify } = require("util");
|
|
5
|
+
const { reservationInventory } = require("../models/repository/inventory.repo");
|
|
6
|
+
const redisClient = redis.createClient();
|
|
7
|
+
|
|
8
|
+
redisClient.ping((err, res) => {
|
|
9
|
+
if (err) {
|
|
10
|
+
console.log("err:::error connecting to redis", err);
|
|
11
|
+
} else {
|
|
12
|
+
console.log("res:::connected to redis", res);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const pexpire = promisify(redisClient.pExpire).bind(redisClient);
|
|
17
|
+
const setnxAsync = promisify(redisClient.setNX).bind(redisClient);
|
|
18
|
+
|
|
19
|
+
const acquireLock = async (productId, quantity, cartId) => {
|
|
20
|
+
const key = `lock_v2024_${productId}`;
|
|
21
|
+
const retryTimes = 10;
|
|
22
|
+
const expireTime = 3000; // 3 seconds thoi gian tam lock
|
|
23
|
+
for (let index = 0; index < retryTimes.length; index++) {
|
|
24
|
+
// tao mot key, thang nao giu key nay duoc vao thanh toan
|
|
25
|
+
const result = await setnxAsync(key, expireTime);
|
|
26
|
+
console.log("result:::", result);
|
|
27
|
+
if (result === 1) {
|
|
28
|
+
// dang co nguoi nam giu key
|
|
29
|
+
// thao tac voi inventory
|
|
30
|
+
const isReserved = await reservationInventory({
|
|
31
|
+
productId,
|
|
32
|
+
cartId,
|
|
33
|
+
quantity,
|
|
34
|
+
});
|
|
35
|
+
// neu reserve trong kho hang thanh cong => giai phong key de nguoi khac co the su dung
|
|
36
|
+
if (isReserved.modifiedCount > 0) {
|
|
37
|
+
await pexpire(key, expireTime);
|
|
38
|
+
return key;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
} else {
|
|
42
|
+
// chua co ai co key
|
|
43
|
+
// thu ket noi lay key 10 lan
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const releaseLock = async (keyLock) => {
|
|
50
|
+
const delAsyncKey = promisify(redisClient.del).bind(redisClient);
|
|
51
|
+
return await delAsyncKey(keyLock);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
acquireLock,
|
|
56
|
+
releaseLock,
|
|
57
|
+
};
|