From 0030e8c9ddd41135da178eea7726b6162769e953 Mon Sep 17 00:00:00 2001 From: ja88a Date: Wed, 22 Jun 2022 16:22:47 +0200 Subject: [PATCH] init mono repo with auth ms v0 --- .eslintignore | 9 + .eslintrc | 23 + .github/dependabot.yml | 19 + .github/workflows/cd.yml | 81 + .github/workflows/ci.yml | 82 + .github/workflows/greetings.yml | 15 + .github/workflows/linter.yml | 31 + .github/workflows/release.yml | 41 + .github/workflows/stale.yml | 23 + .github/workflows/test.yml | 62 + .gitignore | 57 + .husky/pre-commit | 7 + .npmignore | 3 + .travis.yml | 18 + LICENSE | 2 +- Makefile | 28 + README.md | 172 + cspell.json | 41 + lerna.json | 19 + nx.json | 39 + package.json | 95 + packages/auth-api/.dockerignore | 35 + packages/auth-api/.env.docker | 37 + packages/auth-api/.env.example | 37 + packages/auth-api/.env.production | 37 + packages/auth-api/.prettierrc | 5 + packages/auth-api/LICENSE.md | 22 + packages/auth-api/README.md | 145 + packages/auth-api/cspell.json | 41 + packages/auth-api/docker-compose.yml | 46 + packages/auth-api/dockerfile | 15 + .../e2e/auth/auth.change-password.e2e-spec.ts | 226 + packages/auth-api/e2e/auth/auth.constant.ts | 5 + .../auth-api/e2e/auth/auth.login.e2e-spec.ts | 266 + .../auth-api/e2e/auth/auth.public.e2e-spec.ts | 178 + .../e2e/auth/auth.refresh.e2e-spec.ts | 228 + packages/auth-api/e2e/jest.json | 32 + .../permission/permission.admin.e2e-spec.ts | 332 + .../e2e/permission/permission.constant.ts | 54 + .../auth-api/e2e/role/role.admin.e2e-spec.ts | 458 + packages/auth-api/e2e/role/role.constant.ts | 54 + .../e2e/setting/setting.admin.e2e-spec.ts | 199 + .../e2e/setting/setting.common.e2e-spec.ts | 158 + .../auth-api/e2e/setting/setting.constant.ts | 6 + packages/auth-api/e2e/user/files/medium.jpg | Bin 0 -> 308701 bytes packages/auth-api/e2e/user/files/small.jpg | Bin 0 -> 52721 bytes packages/auth-api/e2e/user/files/test.txt | 1 + .../auth-api/e2e/user/user.admin.e2e-spec.ts | 527 + packages/auth-api/e2e/user/user.constant.ts | 10 + .../auth-api/e2e/user/user.public.e2e-spec.ts | 232 + packages/auth-api/endpoints/endpoints.json | 7122 ++++++++++ packages/auth-api/endpoints/env.json | 93 + .../integration/aws/aws.s3.constant.ts | 1 + .../aws/aws.s3.integration.spec.ts | 63 + .../integration/database/database.constant.ts | 1 + .../database/database.integration.spec.ts | 63 + packages/auth-api/integration/jest.json | 32 + packages/auth-api/nest-cli.json | 9 + packages/auth-api/nodemon.json | 5 + packages/auth-api/package.json | 133 + packages/auth-api/scripts/docker-run.sh | 126 + packages/auth-api/src/app/app.module.ts | 19 + .../auth-api/src/app/app.router.module.ts | 64 + packages/auth-api/src/auth/auth.constant.ts | 28 + packages/auth-api/src/auth/auth.decorator.ts | 94 + packages/auth-api/src/auth/auth.interface.ts | 39 + packages/auth-api/src/auth/auth.module.ts | 49 + .../auth/controller/auth.common.controller.ts | 260 + .../auth/controller/auth.public.controller.ts | 129 + .../src/auth/dto/auth.api.create.dto.ts | 20 + .../src/auth/dto/auth.api.update.dto.ts | 20 + .../src/auth/dto/auth.change-password.dto.ts | 12 + .../auth-api/src/auth/dto/auth.login.dto.ts | 27 + .../auth-api/src/auth/dto/auth.sign-up.dto.ts | 47 + .../auth/guard/api-key/auth.api-key.guard.ts | 117 + .../guard/api-key/auth.api-key.strategy.ts | 114 + .../src/auth/guard/basic/auth.basic.guard.ts | 64 + .../jwt-refresh/auth.jwt-refresh.guard.ts | 18 + .../jwt-refresh/auth.jwt-refresh.strategy.ts | 27 + .../src/auth/guard/jwt/auth.jwt.guard.ts | 18 + .../src/auth/guard/jwt/auth.jwt.strategy.ts | 24 + .../guard/payload/auth.payload.admin.guard.ts | 36 + .../payload/auth.payload.default.guard.ts | 30 + .../auth.payload.password-expired.guard.ts | 32 + .../src/auth/schema/auth.api.schema.ts | 52 + .../auth.api.get.serialization.ts | 20 + .../auth.api.list.serialization.ts | 11 + .../serialization/auth.login.serialization.ts | 47 + .../src/auth/service/auth.api.bulk.service.ts | 18 + .../src/auth/service/auth.api.service.ts | 265 + .../auth-api/src/auth/service/auth.service.ts | 177 + packages/auth-api/src/aws/aws.interface.ts | 15 + packages/auth-api/src/aws/aws.module.ts | 10 + .../src/aws/service/aws.s3.service.ts | 201 + packages/auth-api/src/cache/cache.module.ts | 23 + .../cache/service/cache.options.service.ts | 19 + .../src/cache/service/cache.service.ts | 25 + packages/auth-api/src/cli.ts | 23 + packages/auth-api/src/config/app.config.ts | 38 + packages/auth-api/src/config/auth.config.ts | 45 + packages/auth-api/src/config/aws.config.ts | 16 + .../auth-api/src/config/database.config.ts | 13 + packages/auth-api/src/config/file.config.ts | 12 + packages/auth-api/src/config/helper.config.ts | 16 + packages/auth-api/src/config/index.ts | 19 + .../auth-api/src/config/middleware.config.ts | 65 + packages/auth-api/src/config/user.config.ts | 8 + packages/auth-api/src/core/core.module.ts | 63 + .../src/database/database.constant.ts | 1 + .../src/database/database.decorator.ts | 15 + .../src/database/database.interface.ts | 9 + .../auth-api/src/database/database.module.ts | 9 + .../src/database/seeds/auth.api.seed.ts | 65 + .../src/database/seeds/permission.seed.ts | 62 + .../auth-api/src/database/seeds/role.seed.ts | 77 + .../src/database/seeds/seeds.module.ts | 26 + .../src/database/seeds/setting.seed.ts | 62 + .../auth-api/src/database/seeds/user.seed.ts | 82 + .../service/database.options.service.ts | 62 + .../src/debugger/debugger.constant.ts | 1 + .../src/debugger/debugger.interface.ts | 5 + .../auth-api/src/debugger/debugger.module.ts | 11 + .../service/debugger.option.service.ts | 81 + .../src/debugger/service/debugger.service.ts | 38 + .../controller/health.common.controller.ts | 135 + packages/auth-api/src/health/health.module.ts | 10 + .../health/indicator/health.aws.indicator.ts | 26 + .../logger/interceptor/logger.interceptor.ts | 113 + .../auth-api/src/logger/logger.constant.ts | 11 + .../auth-api/src/logger/logger.decorator.ts | 11 + .../auth-api/src/logger/logger.interface.ts | 24 + packages/auth-api/src/logger/logger.module.ts | 28 + .../src/logger/schema/logger.schema.ts | 79 + .../src/logger/service/logger.service.ts | 118 + packages/auth-api/src/main.ts | 77 + .../controller/message.enum.controller.ts | 23 + .../src/message/languages/en/auth.json | 28 + .../src/message/languages/en/file.json | 8 + .../src/message/languages/en/health.json | 6 + .../src/message/languages/en/http.json | 38 + .../src/message/languages/en/message.json | 5 + .../src/message/languages/en/middleware.json | 6 + .../src/message/languages/en/permission.json | 12 + .../src/message/languages/en/request.json | 30 + .../src/message/languages/en/response.json | 6 + .../src/message/languages/en/role.json | 16 + .../src/message/languages/en/setting.json | 9 + .../src/message/languages/en/test.json | 3 + .../src/message/languages/en/user.json | 19 + .../src/message/languages/id/request.json | 3 + .../src/message/languages/id/test.json | 3 + .../auth-api/src/message/message.constant.ts | 4 + .../auth-api/src/message/message.interface.ts | 9 + .../auth-api/src/message/message.module.ts | 32 + .../src/message/service/message.service.ts | 126 + .../src/pagination/pagination.abstract.ts | 20 + .../src/pagination/pagination.constant.ts | 17 + .../src/pagination/pagination.decorator.ts | 200 + .../src/pagination/pagination.interface.ts | 27 + .../src/pagination/pagination.module.ts | 10 + .../pagination/service/pagination.service.ts | 25 + .../controller/permission.admin.controller.ts | 194 + .../src/permission/dto/permission.list.dto.ts | 42 + .../permission/dto/permission.update.dto.ts | 12 + .../permission/dto/permissions.request.dto.ts | 9 + .../guard/payload/permission.default.guard.ts | 47 + .../guard/permission.active.guard.ts | 38 + .../guard/permission.not-found.guard.ts | 23 + .../guard/permission.put-to-request.guard.ts | 20 + .../src/permission/permission.constant.ts | 31 + .../src/permission/permission.decorator.ts | 57 + .../src/permission/permission.interface.ts | 6 + .../src/permission/permission.module.ts | 29 + .../permission/schema/permission.schema.ts | 43 + .../permission.get.serialization.ts | 15 + .../permission.list.serialization.ts | 14 + .../service/permission.bulk.service.ts | 32 + .../permission/service/permission.service.ts | 107 + .../role/controller/role.admin.controller.ts | 278 + .../auth-api/src/role/dto/role.create.dto.ts | 26 + .../auth-api/src/role/dto/role.list.dto.ts | 37 + .../auth-api/src/role/dto/role.request.dto.ts | 9 + .../auth-api/src/role/dto/role.update.dto.ts | 3 + .../src/role/guard/role.active.guard.ts | 37 + .../src/role/guard/role.not-found.guard.ts | 23 + .../role/guard/role.put-to-request.guard.ts | 24 + .../src/role/guard/role.used.guard.ts | 30 + packages/auth-api/src/role/role.constant.ts | 15 + packages/auth-api/src/role/role.decorator.ts | 56 + packages/auth-api/src/role/role.interface.ts | 6 + packages/auth-api/src/role/role.module.ts | 25 + .../auth-api/src/role/schema/role.schema.ts | 46 + .../serialization/role.get.serialization.ts | 26 + .../serialization/role.list.serialization.ts | 18 + .../src/role/service/role.bulk.service.ts | 29 + .../auth-api/src/role/service/role.service.ts | 146 + .../src/router/router.admin.module.ts | 22 + .../src/router/router.callback.module.ts | 9 + .../src/router/router.common.module.ts | 31 + .../auth-api/src/router/router.enum.module.ts | 11 + .../src/router/router.public.module.ts | 16 + .../auth-api/src/router/router.test.module.ts | 10 + .../controller/setting.admin.controller.ts | 54 + .../controller/setting.common.controller.ts | 107 + .../src/setting/dto/setting.create.dto.ts | 22 + .../src/setting/dto/setting.list.dto.ts | 37 + .../src/setting/dto/setting.request.dto.ts | 9 + .../src/setting/dto/setting.update.dto.ts | 15 + .../setting/guard/setting.not-found.guard.ts | 24 + .../guard/setting.put-to-request.guard.ts | 39 + .../src/setting/schema/setting.schema.ts | 30 + .../setting.get.serialization.ts | 12 + .../setting.list.serialization.ts | 12 + .../setting/service/setting.bulk.service.ts | 17 + .../src/setting/service/setting.service.ts | 109 + .../auth-api/src/setting/setting.constant.ts | 9 + .../auth-api/src/setting/setting.decorator.ts | 36 + .../auth-api/src/setting/setting.module.ts | 30 + packages/auth-api/src/task/task.module.ts | 25 + .../controller/testing.common.controller.ts | 63 + .../user/controller/user.admin.controller.ts | 279 + .../user/controller/user.public.controller.ts | 80 + .../auth-api/src/user/dto/user.create.dto.ts | 52 + .../auth-api/src/user/dto/user.list.dto.ts | 37 + .../auth-api/src/user/dto/user.request.dto.ts | 9 + .../auth-api/src/user/dto/user.update.dto.ts | 23 + .../user.payload.put-to-request.guard.ts | 24 + .../src/user/guard/user.active.guard.ts | 37 + .../src/user/guard/user.not-found.guard.ts | 23 + .../user/guard/user.put-to-request.guard.ts | 25 + .../auth-api/src/user/schema/user.schema.ts | 98 + .../serialization/user.get.serialization.ts | 38 + .../serialization/user.list.serialization.ts | 34 + .../user.profile.serialization.ts | 38 + .../src/user/service/user.bulk.service.ts | 17 + .../auth-api/src/user/service/user.service.ts | 261 + packages/auth-api/src/user/user.constant.ts | 26 + packages/auth-api/src/user/user.decorator.ts | 51 + packages/auth-api/src/user/user.interface.ts | 24 + packages/auth-api/src/user/user.module.ts | 25 + .../src/utils/error/error.constant.ts | 8 + .../src/utils/error/error.decorator.ts | 12 + .../src/utils/error/error.interface.ts | 14 + .../auth-api/src/utils/error/error.module.ts | 28 + .../exception/error.success.exception.ts | 18 + .../src/utils/error/filter/error.filter.ts | 65 + .../interceptor/error.log.interceptor.ts | 58 + .../auth-api/src/utils/file/file.constant.ts | 16 + .../auth-api/src/utils/file/file.decorator.ts | 38 + .../auth-api/src/utils/file/file.interface.ts | 1 + .../interceptor/file.image.interceptor.ts | 100 + .../src/utils/helper/helper.constant.ts | 19 + .../src/utils/helper/helper.interface.ts | 51 + .../src/utils/helper/helper.module.ts | 56 + .../helper/service/helper.array.service.ts | 107 + .../helper/service/helper.date.service.ts | 229 + .../service/helper.encryption.service.ts | 75 + .../helper/service/helper.file.service.ts | 53 + .../helper/service/helper.geo.service.ts | 14 + .../helper/service/helper.hash.service.ts | 26 + .../helper/service/helper.number.service.ts | 24 + .../utils/helper/service/helper.service.ts | 8 + .../helper/service/helper.string.service.ts | 109 + .../body-parser/body-parser.middleware.ts | 38 + .../compression/compression.middleware.ts | 10 + .../utils/middleware/cors/cors.middleware.ts | 37 + .../custom-language.middleware.ts | 45 + .../middleware/helmet/helmet.middleware.ts | 10 + .../http-debugger/http-debugger.constant.ts | 3 + .../http-debugger/http-debugger.interface.ts | 15 + .../http-debugger/http-debugger.middleware.ts | 99 + .../maintenance/maintenance.middleware.ts | 33 + .../src/utils/middleware/middleware.module.ts | 85 + .../rate-limit/rate-limit.constant.ts | 2 + .../rate-limit/rate-limit.middleware.ts | 24 + .../request-id/request-id.middleware.ts | 18 + .../response-time/response-time.middleware.ts | 10 + .../timestamp/timestamp.middleware.ts | 33 + .../timezone/timezone.middleware.ts | 31 + .../user-agent/user-agent.middleware.ts | 29 + .../request/guard/request.param.guard.ts | 40 + .../request.timestamp.interceptor.ts | 85 + .../request/pipe/request.add-date.pipe.ts | 20 + .../src/utils/request/request.constant.ts | 18 + .../src/utils/request/request.decorator.ts | 56 + .../src/utils/request/request.interface.ts | 14 + .../src/utils/request/request.module.ts | 77 + .../request.is-password-medium.validation.ts | 45 + .../request.is-password-strong.validation.ts | 46 + .../request.is-password-weak.validation.ts | 42 + .../request.is-start-with.validation.ts | 35 + ...quest.max-greater-than-equal.validation.ts | 36 + .../request.max-greater-than.validation.ts | 34 + ...quest.min-greater-than-equal.validation.ts | 36 + .../request.min-greater-than.validation.ts | 34 + .../request.only-digits.validation.ts | 31 + .../request.safe-string.validation.ts | 30 + .../validation/request.skip.validation.ts | 25 + ....string-or-number-or-boolean.validation.ts | 39 + .../response.custom-headers.interceptor.ts | 50 + .../response.default.interceptor.ts | 67 + .../response.paging.interceptor.ts | 120 + .../response.timeout.interceptor.ts | 92 + .../src/utils/response/response.constant.ts | 1 + .../src/utils/response/response.decorator.ts | 28 + .../src/utils/response/response.interface.ts | 25 + .../src/utils/response/response.module.ts | 23 + .../interceptor/version.interceptor.ts | 46 + .../src/utils/version/version.decorator.ts | 8 + .../src/utils/version/version.interface.ts | 3 + .../src/utils/version/version.module.ts | 18 + .../auth-api/test/auth/auth.service.spec.ts | 470 + .../auth-api/test/cache/cache.options.spec.ts | 39 + .../auth-api/test/cache/cache.service.spec.ts | 103 + .../test/config/config.service.spec.ts | 35 + .../test/database/database.service.spec.ts | 45 + .../debugger/debugger.options.service.spec.ts | 40 + .../test/debugger/debugger.service.spec.ts | 144 + .../test/helper/helper.array.service.spec.ts | 510 + .../test/helper/helper.date.service.spec.ts | 839 ++ .../helper/helper.encryption.service.spec.ts | 281 + .../test/helper/helper.file.service.spec.ts | 37 + .../test/helper/helper.geo.service.spec.ts | 59 + .../test/helper/helper.hash.service.spec.ts | 122 + .../test/helper/helper.number.service.spec.ts | 92 + .../test/helper/helper.service.spec.ts | 37 + .../test/helper/helper.string.service.spec.ts | 317 + packages/auth-api/test/jest.json | 41 + .../test/logger/logger.service.spec.ts | 139 + .../test/message/messaga.service.spec.ts | 274 + .../pagination/pagination.service.spec.ts | 73 + .../test/setting/setting.bulk.service.spec.ts | 42 + .../test/setting/setting.service.spec.ts | 360 + packages/auth-api/tsconfig.build.json | 14 + packages/auth-api/tsconfig.json | 54 + scripts/build.sh | 13 + scripts/clean.sh | 11 + tsconfig.base.json | 39 + tsconfig.build.json | 9 + tsconfig.json | 3 + yarn.lock | 11288 ++++++++++++++++ 341 files changed, 38220 insertions(+), 1 deletion(-) create mode 100755 .eslintignore create mode 100644 .eslintrc create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/greetings.yml create mode 100644 .github/workflows/linter.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/stale.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100755 .husky/pre-commit create mode 100644 .npmignore create mode 100644 .travis.yml create mode 100644 Makefile create mode 100644 README.md create mode 100644 cspell.json create mode 100644 lerna.json create mode 100644 nx.json create mode 100644 package.json create mode 100644 packages/auth-api/.dockerignore create mode 100644 packages/auth-api/.env.docker create mode 100644 packages/auth-api/.env.example create mode 100644 packages/auth-api/.env.production create mode 100644 packages/auth-api/.prettierrc create mode 100644 packages/auth-api/LICENSE.md create mode 100644 packages/auth-api/README.md create mode 100644 packages/auth-api/cspell.json create mode 100644 packages/auth-api/docker-compose.yml create mode 100644 packages/auth-api/dockerfile create mode 100644 packages/auth-api/e2e/auth/auth.change-password.e2e-spec.ts create mode 100644 packages/auth-api/e2e/auth/auth.constant.ts create mode 100644 packages/auth-api/e2e/auth/auth.login.e2e-spec.ts create mode 100644 packages/auth-api/e2e/auth/auth.public.e2e-spec.ts create mode 100644 packages/auth-api/e2e/auth/auth.refresh.e2e-spec.ts create mode 100644 packages/auth-api/e2e/jest.json create mode 100644 packages/auth-api/e2e/permission/permission.admin.e2e-spec.ts create mode 100644 packages/auth-api/e2e/permission/permission.constant.ts create mode 100644 packages/auth-api/e2e/role/role.admin.e2e-spec.ts create mode 100644 packages/auth-api/e2e/role/role.constant.ts create mode 100644 packages/auth-api/e2e/setting/setting.admin.e2e-spec.ts create mode 100644 packages/auth-api/e2e/setting/setting.common.e2e-spec.ts create mode 100644 packages/auth-api/e2e/setting/setting.constant.ts create mode 100644 packages/auth-api/e2e/user/files/medium.jpg create mode 100644 packages/auth-api/e2e/user/files/small.jpg create mode 100644 packages/auth-api/e2e/user/files/test.txt create mode 100644 packages/auth-api/e2e/user/user.admin.e2e-spec.ts create mode 100644 packages/auth-api/e2e/user/user.constant.ts create mode 100644 packages/auth-api/e2e/user/user.public.e2e-spec.ts create mode 100644 packages/auth-api/endpoints/endpoints.json create mode 100644 packages/auth-api/endpoints/env.json create mode 100644 packages/auth-api/integration/aws/aws.s3.constant.ts create mode 100644 packages/auth-api/integration/aws/aws.s3.integration.spec.ts create mode 100644 packages/auth-api/integration/database/database.constant.ts create mode 100644 packages/auth-api/integration/database/database.integration.spec.ts create mode 100644 packages/auth-api/integration/jest.json create mode 100644 packages/auth-api/nest-cli.json create mode 100644 packages/auth-api/nodemon.json create mode 100644 packages/auth-api/package.json create mode 100755 packages/auth-api/scripts/docker-run.sh create mode 100644 packages/auth-api/src/app/app.module.ts create mode 100644 packages/auth-api/src/app/app.router.module.ts create mode 100644 packages/auth-api/src/auth/auth.constant.ts create mode 100644 packages/auth-api/src/auth/auth.decorator.ts create mode 100644 packages/auth-api/src/auth/auth.interface.ts create mode 100644 packages/auth-api/src/auth/auth.module.ts create mode 100644 packages/auth-api/src/auth/controller/auth.common.controller.ts create mode 100644 packages/auth-api/src/auth/controller/auth.public.controller.ts create mode 100644 packages/auth-api/src/auth/dto/auth.api.create.dto.ts create mode 100644 packages/auth-api/src/auth/dto/auth.api.update.dto.ts create mode 100644 packages/auth-api/src/auth/dto/auth.change-password.dto.ts create mode 100644 packages/auth-api/src/auth/dto/auth.login.dto.ts create mode 100644 packages/auth-api/src/auth/dto/auth.sign-up.dto.ts create mode 100644 packages/auth-api/src/auth/guard/api-key/auth.api-key.guard.ts create mode 100644 packages/auth-api/src/auth/guard/api-key/auth.api-key.strategy.ts create mode 100644 packages/auth-api/src/auth/guard/basic/auth.basic.guard.ts create mode 100644 packages/auth-api/src/auth/guard/jwt-refresh/auth.jwt-refresh.guard.ts create mode 100644 packages/auth-api/src/auth/guard/jwt-refresh/auth.jwt-refresh.strategy.ts create mode 100644 packages/auth-api/src/auth/guard/jwt/auth.jwt.guard.ts create mode 100644 packages/auth-api/src/auth/guard/jwt/auth.jwt.strategy.ts create mode 100644 packages/auth-api/src/auth/guard/payload/auth.payload.admin.guard.ts create mode 100644 packages/auth-api/src/auth/guard/payload/auth.payload.default.guard.ts create mode 100644 packages/auth-api/src/auth/guard/payload/auth.payload.password-expired.guard.ts create mode 100644 packages/auth-api/src/auth/schema/auth.api.schema.ts create mode 100644 packages/auth-api/src/auth/serialization/auth.api.get.serialization.ts create mode 100644 packages/auth-api/src/auth/serialization/auth.api.list.serialization.ts create mode 100644 packages/auth-api/src/auth/serialization/auth.login.serialization.ts create mode 100644 packages/auth-api/src/auth/service/auth.api.bulk.service.ts create mode 100644 packages/auth-api/src/auth/service/auth.api.service.ts create mode 100644 packages/auth-api/src/auth/service/auth.service.ts create mode 100644 packages/auth-api/src/aws/aws.interface.ts create mode 100644 packages/auth-api/src/aws/aws.module.ts create mode 100644 packages/auth-api/src/aws/service/aws.s3.service.ts create mode 100644 packages/auth-api/src/cache/cache.module.ts create mode 100644 packages/auth-api/src/cache/service/cache.options.service.ts create mode 100644 packages/auth-api/src/cache/service/cache.service.ts create mode 100644 packages/auth-api/src/cli.ts create mode 100644 packages/auth-api/src/config/app.config.ts create mode 100644 packages/auth-api/src/config/auth.config.ts create mode 100644 packages/auth-api/src/config/aws.config.ts create mode 100644 packages/auth-api/src/config/database.config.ts create mode 100644 packages/auth-api/src/config/file.config.ts create mode 100644 packages/auth-api/src/config/helper.config.ts create mode 100644 packages/auth-api/src/config/index.ts create mode 100644 packages/auth-api/src/config/middleware.config.ts create mode 100644 packages/auth-api/src/config/user.config.ts create mode 100644 packages/auth-api/src/core/core.module.ts create mode 100644 packages/auth-api/src/database/database.constant.ts create mode 100644 packages/auth-api/src/database/database.decorator.ts create mode 100644 packages/auth-api/src/database/database.interface.ts create mode 100644 packages/auth-api/src/database/database.module.ts create mode 100644 packages/auth-api/src/database/seeds/auth.api.seed.ts create mode 100644 packages/auth-api/src/database/seeds/permission.seed.ts create mode 100644 packages/auth-api/src/database/seeds/role.seed.ts create mode 100644 packages/auth-api/src/database/seeds/seeds.module.ts create mode 100644 packages/auth-api/src/database/seeds/setting.seed.ts create mode 100644 packages/auth-api/src/database/seeds/user.seed.ts create mode 100644 packages/auth-api/src/database/service/database.options.service.ts create mode 100644 packages/auth-api/src/debugger/debugger.constant.ts create mode 100644 packages/auth-api/src/debugger/debugger.interface.ts create mode 100644 packages/auth-api/src/debugger/debugger.module.ts create mode 100644 packages/auth-api/src/debugger/service/debugger.option.service.ts create mode 100644 packages/auth-api/src/debugger/service/debugger.service.ts create mode 100644 packages/auth-api/src/health/controller/health.common.controller.ts create mode 100644 packages/auth-api/src/health/health.module.ts create mode 100644 packages/auth-api/src/health/indicator/health.aws.indicator.ts create mode 100644 packages/auth-api/src/logger/interceptor/logger.interceptor.ts create mode 100644 packages/auth-api/src/logger/logger.constant.ts create mode 100644 packages/auth-api/src/logger/logger.decorator.ts create mode 100644 packages/auth-api/src/logger/logger.interface.ts create mode 100644 packages/auth-api/src/logger/logger.module.ts create mode 100644 packages/auth-api/src/logger/schema/logger.schema.ts create mode 100644 packages/auth-api/src/logger/service/logger.service.ts create mode 100644 packages/auth-api/src/main.ts create mode 100644 packages/auth-api/src/message/controller/message.enum.controller.ts create mode 100644 packages/auth-api/src/message/languages/en/auth.json create mode 100644 packages/auth-api/src/message/languages/en/file.json create mode 100644 packages/auth-api/src/message/languages/en/health.json create mode 100644 packages/auth-api/src/message/languages/en/http.json create mode 100644 packages/auth-api/src/message/languages/en/message.json create mode 100644 packages/auth-api/src/message/languages/en/middleware.json create mode 100644 packages/auth-api/src/message/languages/en/permission.json create mode 100644 packages/auth-api/src/message/languages/en/request.json create mode 100644 packages/auth-api/src/message/languages/en/response.json create mode 100644 packages/auth-api/src/message/languages/en/role.json create mode 100644 packages/auth-api/src/message/languages/en/setting.json create mode 100644 packages/auth-api/src/message/languages/en/test.json create mode 100644 packages/auth-api/src/message/languages/en/user.json create mode 100644 packages/auth-api/src/message/languages/id/request.json create mode 100644 packages/auth-api/src/message/languages/id/test.json create mode 100644 packages/auth-api/src/message/message.constant.ts create mode 100644 packages/auth-api/src/message/message.interface.ts create mode 100644 packages/auth-api/src/message/message.module.ts create mode 100644 packages/auth-api/src/message/service/message.service.ts create mode 100644 packages/auth-api/src/pagination/pagination.abstract.ts create mode 100644 packages/auth-api/src/pagination/pagination.constant.ts create mode 100644 packages/auth-api/src/pagination/pagination.decorator.ts create mode 100644 packages/auth-api/src/pagination/pagination.interface.ts create mode 100644 packages/auth-api/src/pagination/pagination.module.ts create mode 100644 packages/auth-api/src/pagination/service/pagination.service.ts create mode 100644 packages/auth-api/src/permission/controller/permission.admin.controller.ts create mode 100644 packages/auth-api/src/permission/dto/permission.list.dto.ts create mode 100644 packages/auth-api/src/permission/dto/permission.update.dto.ts create mode 100644 packages/auth-api/src/permission/dto/permissions.request.dto.ts create mode 100644 packages/auth-api/src/permission/guard/payload/permission.default.guard.ts create mode 100644 packages/auth-api/src/permission/guard/permission.active.guard.ts create mode 100644 packages/auth-api/src/permission/guard/permission.not-found.guard.ts create mode 100644 packages/auth-api/src/permission/guard/permission.put-to-request.guard.ts create mode 100644 packages/auth-api/src/permission/permission.constant.ts create mode 100644 packages/auth-api/src/permission/permission.decorator.ts create mode 100644 packages/auth-api/src/permission/permission.interface.ts create mode 100644 packages/auth-api/src/permission/permission.module.ts create mode 100644 packages/auth-api/src/permission/schema/permission.schema.ts create mode 100644 packages/auth-api/src/permission/serialization/permission.get.serialization.ts create mode 100644 packages/auth-api/src/permission/serialization/permission.list.serialization.ts create mode 100644 packages/auth-api/src/permission/service/permission.bulk.service.ts create mode 100644 packages/auth-api/src/permission/service/permission.service.ts create mode 100644 packages/auth-api/src/role/controller/role.admin.controller.ts create mode 100644 packages/auth-api/src/role/dto/role.create.dto.ts create mode 100644 packages/auth-api/src/role/dto/role.list.dto.ts create mode 100644 packages/auth-api/src/role/dto/role.request.dto.ts create mode 100644 packages/auth-api/src/role/dto/role.update.dto.ts create mode 100644 packages/auth-api/src/role/guard/role.active.guard.ts create mode 100644 packages/auth-api/src/role/guard/role.not-found.guard.ts create mode 100644 packages/auth-api/src/role/guard/role.put-to-request.guard.ts create mode 100644 packages/auth-api/src/role/guard/role.used.guard.ts create mode 100644 packages/auth-api/src/role/role.constant.ts create mode 100644 packages/auth-api/src/role/role.decorator.ts create mode 100644 packages/auth-api/src/role/role.interface.ts create mode 100644 packages/auth-api/src/role/role.module.ts create mode 100644 packages/auth-api/src/role/schema/role.schema.ts create mode 100644 packages/auth-api/src/role/serialization/role.get.serialization.ts create mode 100644 packages/auth-api/src/role/serialization/role.list.serialization.ts create mode 100644 packages/auth-api/src/role/service/role.bulk.service.ts create mode 100644 packages/auth-api/src/role/service/role.service.ts create mode 100644 packages/auth-api/src/router/router.admin.module.ts create mode 100644 packages/auth-api/src/router/router.callback.module.ts create mode 100644 packages/auth-api/src/router/router.common.module.ts create mode 100644 packages/auth-api/src/router/router.enum.module.ts create mode 100644 packages/auth-api/src/router/router.public.module.ts create mode 100644 packages/auth-api/src/router/router.test.module.ts create mode 100644 packages/auth-api/src/setting/controller/setting.admin.controller.ts create mode 100644 packages/auth-api/src/setting/controller/setting.common.controller.ts create mode 100644 packages/auth-api/src/setting/dto/setting.create.dto.ts create mode 100644 packages/auth-api/src/setting/dto/setting.list.dto.ts create mode 100644 packages/auth-api/src/setting/dto/setting.request.dto.ts create mode 100644 packages/auth-api/src/setting/dto/setting.update.dto.ts create mode 100644 packages/auth-api/src/setting/guard/setting.not-found.guard.ts create mode 100644 packages/auth-api/src/setting/guard/setting.put-to-request.guard.ts create mode 100644 packages/auth-api/src/setting/schema/setting.schema.ts create mode 100644 packages/auth-api/src/setting/serialization/setting.get.serialization.ts create mode 100644 packages/auth-api/src/setting/serialization/setting.list.serialization.ts create mode 100644 packages/auth-api/src/setting/service/setting.bulk.service.ts create mode 100644 packages/auth-api/src/setting/service/setting.service.ts create mode 100644 packages/auth-api/src/setting/setting.constant.ts create mode 100644 packages/auth-api/src/setting/setting.decorator.ts create mode 100644 packages/auth-api/src/setting/setting.module.ts create mode 100644 packages/auth-api/src/task/task.module.ts create mode 100644 packages/auth-api/src/testing/controller/testing.common.controller.ts create mode 100644 packages/auth-api/src/user/controller/user.admin.controller.ts create mode 100644 packages/auth-api/src/user/controller/user.public.controller.ts create mode 100644 packages/auth-api/src/user/dto/user.create.dto.ts create mode 100644 packages/auth-api/src/user/dto/user.list.dto.ts create mode 100644 packages/auth-api/src/user/dto/user.request.dto.ts create mode 100644 packages/auth-api/src/user/dto/user.update.dto.ts create mode 100644 packages/auth-api/src/user/guard/payload/user.payload.put-to-request.guard.ts create mode 100644 packages/auth-api/src/user/guard/user.active.guard.ts create mode 100644 packages/auth-api/src/user/guard/user.not-found.guard.ts create mode 100644 packages/auth-api/src/user/guard/user.put-to-request.guard.ts create mode 100644 packages/auth-api/src/user/schema/user.schema.ts create mode 100644 packages/auth-api/src/user/serialization/user.get.serialization.ts create mode 100644 packages/auth-api/src/user/serialization/user.list.serialization.ts create mode 100644 packages/auth-api/src/user/serialization/user.profile.serialization.ts create mode 100644 packages/auth-api/src/user/service/user.bulk.service.ts create mode 100644 packages/auth-api/src/user/service/user.service.ts create mode 100644 packages/auth-api/src/user/user.constant.ts create mode 100644 packages/auth-api/src/user/user.decorator.ts create mode 100644 packages/auth-api/src/user/user.interface.ts create mode 100644 packages/auth-api/src/user/user.module.ts create mode 100644 packages/auth-api/src/utils/error/error.constant.ts create mode 100644 packages/auth-api/src/utils/error/error.decorator.ts create mode 100644 packages/auth-api/src/utils/error/error.interface.ts create mode 100644 packages/auth-api/src/utils/error/error.module.ts create mode 100644 packages/auth-api/src/utils/error/exception/error.success.exception.ts create mode 100644 packages/auth-api/src/utils/error/filter/error.filter.ts create mode 100644 packages/auth-api/src/utils/error/interceptor/error.log.interceptor.ts create mode 100644 packages/auth-api/src/utils/file/file.constant.ts create mode 100644 packages/auth-api/src/utils/file/file.decorator.ts create mode 100644 packages/auth-api/src/utils/file/file.interface.ts create mode 100644 packages/auth-api/src/utils/file/interceptor/file.image.interceptor.ts create mode 100644 packages/auth-api/src/utils/helper/helper.constant.ts create mode 100644 packages/auth-api/src/utils/helper/helper.interface.ts create mode 100644 packages/auth-api/src/utils/helper/helper.module.ts create mode 100644 packages/auth-api/src/utils/helper/service/helper.array.service.ts create mode 100644 packages/auth-api/src/utils/helper/service/helper.date.service.ts create mode 100644 packages/auth-api/src/utils/helper/service/helper.encryption.service.ts create mode 100644 packages/auth-api/src/utils/helper/service/helper.file.service.ts create mode 100644 packages/auth-api/src/utils/helper/service/helper.geo.service.ts create mode 100644 packages/auth-api/src/utils/helper/service/helper.hash.service.ts create mode 100644 packages/auth-api/src/utils/helper/service/helper.number.service.ts create mode 100644 packages/auth-api/src/utils/helper/service/helper.service.ts create mode 100644 packages/auth-api/src/utils/helper/service/helper.string.service.ts create mode 100644 packages/auth-api/src/utils/middleware/body-parser/body-parser.middleware.ts create mode 100644 packages/auth-api/src/utils/middleware/compression/compression.middleware.ts create mode 100644 packages/auth-api/src/utils/middleware/cors/cors.middleware.ts create mode 100644 packages/auth-api/src/utils/middleware/custom-language/custom-language.middleware.ts create mode 100644 packages/auth-api/src/utils/middleware/helmet/helmet.middleware.ts create mode 100644 packages/auth-api/src/utils/middleware/http-debugger/http-debugger.constant.ts create mode 100644 packages/auth-api/src/utils/middleware/http-debugger/http-debugger.interface.ts create mode 100644 packages/auth-api/src/utils/middleware/http-debugger/http-debugger.middleware.ts create mode 100644 packages/auth-api/src/utils/middleware/maintenance/maintenance.middleware.ts create mode 100644 packages/auth-api/src/utils/middleware/middleware.module.ts create mode 100644 packages/auth-api/src/utils/middleware/rate-limit/rate-limit.constant.ts create mode 100644 packages/auth-api/src/utils/middleware/rate-limit/rate-limit.middleware.ts create mode 100644 packages/auth-api/src/utils/middleware/request-id/request-id.middleware.ts create mode 100644 packages/auth-api/src/utils/middleware/response-time/response-time.middleware.ts create mode 100644 packages/auth-api/src/utils/middleware/timestamp/timestamp.middleware.ts create mode 100644 packages/auth-api/src/utils/middleware/timezone/timezone.middleware.ts create mode 100644 packages/auth-api/src/utils/middleware/user-agent/user-agent.middleware.ts create mode 100644 packages/auth-api/src/utils/request/guard/request.param.guard.ts create mode 100644 packages/auth-api/src/utils/request/interceptor/request.timestamp.interceptor.ts create mode 100644 packages/auth-api/src/utils/request/pipe/request.add-date.pipe.ts create mode 100644 packages/auth-api/src/utils/request/request.constant.ts create mode 100644 packages/auth-api/src/utils/request/request.decorator.ts create mode 100644 packages/auth-api/src/utils/request/request.interface.ts create mode 100644 packages/auth-api/src/utils/request/request.module.ts create mode 100644 packages/auth-api/src/utils/request/validation/request.is-password-medium.validation.ts create mode 100644 packages/auth-api/src/utils/request/validation/request.is-password-strong.validation.ts create mode 100644 packages/auth-api/src/utils/request/validation/request.is-password-weak.validation.ts create mode 100644 packages/auth-api/src/utils/request/validation/request.is-start-with.validation.ts create mode 100644 packages/auth-api/src/utils/request/validation/request.max-greater-than-equal.validation.ts create mode 100644 packages/auth-api/src/utils/request/validation/request.max-greater-than.validation.ts create mode 100644 packages/auth-api/src/utils/request/validation/request.min-greater-than-equal.validation.ts create mode 100644 packages/auth-api/src/utils/request/validation/request.min-greater-than.validation.ts create mode 100644 packages/auth-api/src/utils/request/validation/request.only-digits.validation.ts create mode 100644 packages/auth-api/src/utils/request/validation/request.safe-string.validation.ts create mode 100644 packages/auth-api/src/utils/request/validation/request.skip.validation.ts create mode 100644 packages/auth-api/src/utils/request/validation/request.string-or-number-or-boolean.validation.ts create mode 100644 packages/auth-api/src/utils/response/interceptor/response.custom-headers.interceptor.ts create mode 100644 packages/auth-api/src/utils/response/interceptor/response.default.interceptor.ts create mode 100644 packages/auth-api/src/utils/response/interceptor/response.paging.interceptor.ts create mode 100644 packages/auth-api/src/utils/response/interceptor/response.timeout.interceptor.ts create mode 100644 packages/auth-api/src/utils/response/response.constant.ts create mode 100644 packages/auth-api/src/utils/response/response.decorator.ts create mode 100644 packages/auth-api/src/utils/response/response.interface.ts create mode 100644 packages/auth-api/src/utils/response/response.module.ts create mode 100644 packages/auth-api/src/utils/version/interceptor/version.interceptor.ts create mode 100644 packages/auth-api/src/utils/version/version.decorator.ts create mode 100644 packages/auth-api/src/utils/version/version.interface.ts create mode 100644 packages/auth-api/src/utils/version/version.module.ts create mode 100644 packages/auth-api/test/auth/auth.service.spec.ts create mode 100644 packages/auth-api/test/cache/cache.options.spec.ts create mode 100644 packages/auth-api/test/cache/cache.service.spec.ts create mode 100644 packages/auth-api/test/config/config.service.spec.ts create mode 100644 packages/auth-api/test/database/database.service.spec.ts create mode 100644 packages/auth-api/test/debugger/debugger.options.service.spec.ts create mode 100644 packages/auth-api/test/debugger/debugger.service.spec.ts create mode 100644 packages/auth-api/test/helper/helper.array.service.spec.ts create mode 100644 packages/auth-api/test/helper/helper.date.service.spec.ts create mode 100644 packages/auth-api/test/helper/helper.encryption.service.spec.ts create mode 100644 packages/auth-api/test/helper/helper.file.service.spec.ts create mode 100644 packages/auth-api/test/helper/helper.geo.service.spec.ts create mode 100644 packages/auth-api/test/helper/helper.hash.service.spec.ts create mode 100644 packages/auth-api/test/helper/helper.number.service.spec.ts create mode 100644 packages/auth-api/test/helper/helper.service.spec.ts create mode 100644 packages/auth-api/test/helper/helper.string.service.spec.ts create mode 100644 packages/auth-api/test/jest.json create mode 100644 packages/auth-api/test/logger/logger.service.spec.ts create mode 100644 packages/auth-api/test/message/messaga.service.spec.ts create mode 100644 packages/auth-api/test/pagination/pagination.service.spec.ts create mode 100644 packages/auth-api/test/setting/setting.bulk.service.spec.ts create mode 100644 packages/auth-api/test/setting/setting.service.spec.ts create mode 100644 packages/auth-api/tsconfig.build.json create mode 100644 packages/auth-api/tsconfig.json create mode 100755 scripts/build.sh create mode 100755 scripts/clean.sh create mode 100644 tsconfig.base.json create mode 100644 tsconfig.build.json create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/.eslintignore b/.eslintignore new file mode 100755 index 0000000..0502376 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,9 @@ +# /node_modules/* in the project root is ignored by default +# build artefacts +dist/* +coverage/* +node_modules/* +logs/* +prod/* +.husky/* +.github/* diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..11a8136 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,23 @@ +{ + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "tsconfig.json", + "sourceType": "module" + }, + "plugins": ["@typescript-eslint/eslint-plugin"], + "extends": [ + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + "root": true, + "env": { + "node": true, + "jest": true + }, + "rules": { + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-explicit-any": "off" + } +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..13c0ea4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ + +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "monthly" + time: "01:00" + open-pull-requests-limit: 10 + commit-message: + prefix: "npm" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + time: "01:00" + open-pull-requests-limit: 5 + commit-message: + prefix: "github-action" \ No newline at end of file diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..b71fbae --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,81 @@ +name: CD + +on: + # workflow_run: + # workflows: ['CI'] + # types: + # - completed + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + env: + APP_NAME: ack-nestjs-boilerplate-mongoose + APP_PORT: 3000 + APP_NETWORK: app-network + + steps: + + - name: Git checkout + uses: actions/checkout@v3 + + - name: Get commit + id: git + run: | + echo "::set-output name=short_sha::$(git rev-parse --short HEAD)" + + - name: Get latest version + id: version + uses: martinbeentjes/npm-get-version-action@main + + - name: Git + run: | + echo Branch name is: ${{ github.ref_name }} + echo Short sha: ${{ steps.git.outputs.short_sha}} + echo Version is: ${{ steps.version.outputs.current-version}} + + - name: Environment + run: | + echo APP_NAME is: ${{ env.APP_NAME}} + echo APP_PORT is: ${{ env.APP_PORT}} + echo APP_NETWORK is: ${{ env.APP_NETWORK}} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Deploy + uses: fifsky/ssh-action@master + with: + command: | + docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.APP_NAME}}:${{ steps.git.outputs.short_sha }} + docker stop ${{ env.APP_NAME}} && docker rm ${{ env.APP_NAME}} + docker network create ${{ env.APP_NETWORK }} --driver=bridge + docker run -itd \ + --hostname ${{ env.APP_NAME}} \ + --publish ${{ env.APP_PORT }}:${{ env.APP_PORT }} \ + --network ${{ env.APP_NETWORK }} \ + --volume /app/${{ env.APP_NAME}}/logs/:/app/logs/ \ + --volume /app/${{ env.APP_NAME}}/.env:/app/.env \ + --restart unless-stopped \ + --name ${{ env.APP_NAME}} ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.APP_NAME}}:v${{ steps.version.outputs.current-version}} + host: ${{ secrets.SSH_HOST }} + port: ${{ secrets.SSH_PORT }} + user: ${{ secrets.SSH_USER }} + key: ${{ secrets.SSH_PRIVATE_KEY}} + + - name: Clean + uses: fifsky/ssh-action@master + continue-on-error: true + with: + command: | + docker container prune --force + docker image prune --force + docker rmi $(docker images **/${{ secrets.APP_NAME }} -q) --force + host: ${{ secrets.SSH_HOST }} + port: ${{ secrets.SSH_PORT }} + user: ${{ secrets.SSH_USER }} + key: ${{ secrets.SSH_PRIVATE_KEY}} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2fc0e5c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,82 @@ +name: CI +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + env: + APP_NAME: ack-nestjs-boilerplate-mongoose + DOCKERFILE: ./prod/dockerfile + + steps: + - name: Git checkout + uses: actions/checkout@v3 + + - name: Get commit + id: git + run: | + echo "::set-output name=short_sha::$(git rev-parse --short HEAD)" + + - name: Get latest version + id: version + uses: martinbeentjes/npm-get-version-action@main + + - name: Git + run: | + echo Branch name is: ${{ github.ref_name }} + echo Short sha: ${{ steps.git.outputs.short_sha}} + echo Version is: ${{ steps.version.outputs.current-version}} + + - name: Environment + run: | + echo APP_NAME is: ${{ env.APP_NAME}} + echo DOCKERFILE is: ${{ env.DOCKERFILE}} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - uses: docker/setup-buildx-action@v2 + id: builder + + - uses: docker/setup-buildx-action@v2 + id: main + + - name: Builder name + run: echo ${{ steps.builder.outputs.name }} + + - name: Main builder name + run: echo ${{ steps.main.outputs.name }} + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build against builder + uses: docker/build-push-action@v3 + with: + builder: ${{ steps.builder.outputs.name }} + file: ${{ env.DOCKERFILE }} + target: builder + + - name: Build against main and push + uses: docker/build-push-action@v3 + with: + builder: ${{ steps.main.outputs.name }} + file: ${{ env.DOCKERFILE }} + target: main + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.APP_NAME }}:latest + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.APP_NAME }}:v${{ steps.version.outputs.current-version}} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.APP_NAME }}:v${{ steps.version.outputs.current-version}}_sha-${{ steps.git.outputs.short_sha }} + push: true + + + diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml new file mode 100644 index 0000000..1a79e83 --- /dev/null +++ b/.github/workflows/greetings.yml @@ -0,0 +1,15 @@ +name: Greetings +on: [pull_request, issues] + +jobs: + greeting: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-message: 'Thank you for opening your first issue here! Please be patient until your request is processed 🚀' + pr-message: 'Thank you for opening this pull request! Please be patient until your changes are reviewed 💌' diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 0000000..31074d4 --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,31 @@ +name: Linter +on: + pull_request: + branches: + - main + +jobs: + linter: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: ['17.x'] + + steps: + - name: Git checkout + uses: actions/checkout@v3 + + - name: Git sort sha + run: echo ${{ steps.vars.outputs.sha_short }} + + - name: Setup node version ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Install dependencies + run: yarn --frozen-lockfile + + - name: Linter + run: yarn lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0773bb5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +name: Release + +on: + workflow_run: + workflows: ['CI'] + types: + - completed + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Git checkout + uses: actions/checkout@v3 + + - name: Get commit + id: git + run: | + echo "::set-output name=short_sha::$(git rev-parse --short HEAD)" + + - name: Get latest version + id: version + uses: martinbeentjes/npm-get-version-action@main + + - name: Git + run: | + echo Branch name is: ${{ github.ref_name }} + echo Short sha: ${{ steps.git.outputs.short_sha}} + echo Version is: ${{ steps.version.outputs.current-version}} + + - name: Release + uses: softprops/action-gh-release@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ steps.version.outputs.current-version}} + name: v${{ steps.version.outputs.current-version}} + generate_release_notes: true + draft: false + prerelease: false diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..7fb5eac --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,23 @@ +name: Close inactive issues +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v5 + with: + days-before-issue-stale: 14 + days-before-issue-close: 7 + stale-issue-label: "stale" + stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." + close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..96ea36f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,62 @@ +name: Test +on: + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + environment: baibay + + strategy: + matrix: + node-version: ['17.x'] + + services: + mongodb: + image: mongo:latest + options: >- + --health-cmd mongo + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 27017:27017 + + steps: + - name: Git checkout + uses: actions/checkout@v3 + + - name: Setup node version ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Install dependencies + run: yarn --frozen-lockfile + + - name: Migration + run: yarn migrate + + - name: Create env file + run: | + touch .env + echo AWS_CREDENTIAL_KEY="$AWS_CREDENTIAL_KEY" >> .env + echo AWS_CREDENTIAL_SECRET="$AWS_CREDENTIAL_SECRET" >> .env + echo AWS_S3_BUCKET="$AWS_S3_BUCKET" >> .env + echo AWS_S3_REGION="$AWS_S3_REGION" >> .env + env: + AWS_CREDENTIAL_KEY: ${{ secrets.AWS_CREDENTIAL_KEY }} + AWS_CREDENTIAL_SECRET: ${{ secrets.AWS_CREDENTIAL_SECRET }} + AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} + AWS_S3_REGION: ${{ secrets.AWS_S3_REGION }} + + - name: Unit Test + run: yarn test:unit + + - name: Unit Integration + run: yarn test:integration + + - name: E2E Test + run: yarn test:e2e diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2319576 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# compiled output +dist +prod +node_modules + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +coverage +e2e-coverage +integration-coverage +.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace +*.code-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# yarn +.yarn/* +.pnp.* +!.yarn/cache +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# environment +.env +config.yaml +config.yml + +# misc +_* +!.husky/_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..2815ab1 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,7 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn lint +yarn deadcode +yarn spell +yarn test \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..c4e4466 --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +*.ts +*.spec.ts +!*.d.ts \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..239d1ba --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: node_js +node_js: + - lts/* + - node +addons: + firefox: latest +cache: + directories: + - node_modules +before_script: + - npm install -g lerna typescript + - lerna bootstrap + - lerna link + - lerna run build +script: + - lerna run test +after_success: + - lerna run coverage \ No newline at end of file diff --git a/LICENSE b/LICENSE index f762d56..d496959 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Jabba +Copyright (c) 2022 Jabba01 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ac4235d --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +check?=schedule + +bootstrap: + lerna bootstrap + lerna link + lerna run build + +docker: + docker build --build-arg module=$(module) --build-arg name=$(name) -f Dockerfile . -t $(repo)$(if $(name),$(name),$(module))-module $(if $(tag), -t $(repo)$(tag), ) + +zip: deps build + (cd $(module)/dist && zip $(if $(name),$(name),$(module))-module.zip index.js) + +clean: + lerna run clean + +reset: + lerna run reset + yarn reset + +deps: clean + # Restore all dependencies + yarn + lerna bootstrap + yarn --frozen-lockfile --production + +build: + lerna run build diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b54051 --- /dev/null +++ b/README.md @@ -0,0 +1,172 @@ + + +[![NestJs][nestjs-shield]][ref-nestjs] +[![NodeJs][nodejs-shield]][ref-nodejs] +[![Typescript][typescript-shield]][ref-typescript] +[![MongoDB][mongodb-shield]][ref-mongodb] +[![JWT][jwt-shield]][ref-jwt] +[![Jest][jest-shield]][ref-jest] +[![Yarn][yarn-shield]][ref-yarn] +[![Docker][docker-shield]][ref-docker] + +# S*OpenAPI Mono-Repository Root + +## Purpose + +> A set of Set of NestJs API Services for Restful API, Microservice, or SaaS Project + +Made with following best practices: +- [nodejs-best-practice](https://github.com/goldbergyoni/nodebestpractices) +- [The Twelve-Factor App](https://12factor.net) +- [Microservice Architecture](https://microservices.io) +- NestJs Habit. + +## Important + +> Still on trial phase, tests will be based on real projects / specific cases. Updates, customizations and new features are still required. + +You can [Request Feature][sopenapi-issues] or [Report Bug][sopenapi-issues] + +## Built With + +Describes which version of the main packages and main tools. + +| Name | Version | +| ---------- | -------- | +| [NestJs](ref-nestjs) | v8.x | +| [NodeJs](ref-nodejs) | v17.x | +| [Typescript](ref-typescript) | v4.x | +| [Mongoose](ref-mongoose) | v6.x | +| [MongoDB](ref-mongodb) | v5.x | +| [Yarn](ref-yarn) | v1.x | +| [NPM](ref-npm) | v8.x | +| [Docker](ref-docker) | v20.x | +| [Docker Compose](ref-docker-compose) | v2.x | + + +## Local Dev Instructions + +### Init your dev environment + +``` +lerna bootstrap +lerna link +lerna run build +``` +OR +``` +make bootstrap +``` + +### Test + +``` +lerna run test +``` + +## Features + +- Authentication and Authorization (OAuth2, API Key, Basic Auth) +- MongoDB integration using Mongoose +- Database Migration +- Integrate with AWS +- Server Side Pagination +- Url Versioning +- Request Validation Pipe +- Custom error status code +- Logger and Debugger +- Centralize Configuration +- Centralize Exception Filter +- Multi-language (i18n) +- Dynamic Setting from Database 🗿 +- Maintenance Mode on / off +- Support Docker Installation +- Support CI/CD with Github Action or Jenkins +- Husky GitHook For Check Source Code, and Run Test Before Commit +- Linter with EsLint for Typescript + +## Prerequisites + +1. Understand [NestJs Fundamental](http://nestjs.com), Main Framework. NodeJs Framework with support fully TypeScript. +2. Understand[Typescript Fundamental](https://www.typescriptlang.org), Programming Language. It will help us to write and read the code. +3. Understand [ExpressJs Fundamental](https://nodejs.org), a NodeJs based Framework. It helps in understanding how the NestJs Framework works. +4. Understand what NoSql is and how it works as a database, especially [MongoDB.](https://docs.mongodb.com) + +## Todo + +Next developments: +- [ ] Play on the prod MS granularity, via Nestjs composition & gnerated Docker images +- [ ] Configs management across all modules +- [ ] Complete CI/CD of modules using Github actions +- [ ] Integrate Terraform +- [ ] Deploy on Azure Kubernetes Services + +## License + +Distributed under [MIT licensed][license]. + + +## Contact + +[Jabba 01][author-email] + +[![Github][github-shield]][author-github] +[![LinkedIn][linkedin-shield]][author-linkedin] + + +[sopenapi-contributors-shield]: https://img.shields.io/github/contributors/ja88a/openapi-nestjs-auth-mongo?style=for-the-badge +[sopenapi-forks-shield]: https://img.shields.io/github/forks/ja88a/openapi-nestjs-auth-mongo?style=for-the-badge +[sopenapi-stars-shield]: https://img.shields.io/github/stars/ja88a/openapi-nestjs-auth-mongo?style=for-the-badge +[sopenapi-issues-shield]: https://img.shields.io/github/issues/ja88a/openapi-nestjs-auth-mongo?style=for-the-badge +[sopenapi-license-shield]: https://img.shields.io/github/license/ja88a/openapi-nestjs-auth-mongo?style=for-the-badge + +[nestjs-shield]: https://img.shields.io/badge/nestjs-%23E0234E.svg?style=for-the-badge&logo=nestjs&logoColor=white +[nodejs-shield]: https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=nodedotjs&logoColor=white +[typescript-shield]: https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white +[mongodb-shield]: https://img.shields.io/badge/MongoDB-white?style=for-the-badge&logo=mongodb&logoColor=4EA94B +[jwt-shield]: https://img.shields.io/badge/JWT-000000?style=for-the-badge&logo=JSON%20web%20tokens&logoColor=white +[jest-shield]: https://img.shields.io/badge/-jest-%23C21325?style=for-the-badge&logo=jest&logoColor=white +[yarn-shield]: https://img.shields.io/badge/yarn-%232C8EBB.svg?style=for-the-badge&logo=yarn&logoColor=white +[docker-shield]: https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white + +[github-shield]: https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white +[linkedin-shield]: https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white + + +[author-linkedin]: https://linkedin.com/in/srenault +[author-email]: mailto:r0g3r@tuta.io +[author-github]: https://github.com/ja88a + + +[sopenapi-endpoint]: /endpoints/endpoints.json + + +[license]: LICENSE.md +[endpoints]: endpoints.json + + +[sopenapi-docs]: https://andrechristikan.github.io/ack-nestjs-boilerplate-docs/ +[sopenapi-docs-features]: https://andrechristikan.github.io/ack-nestjs-boilerplate-docs/#/features/readme +[sopenapi-docs-example]: https://andrechristikan.github.io/ack-nestjs-boilerplate-docs/#/example +[sopenapi-docs-tips]: https://andrechristikan.github.io/ack-nestjs-boilerplate-docs/#/tips/readme +[sopenapi-doc-env]: https://andrechristikan.github.io/ack-nestjs-boilerplate-docs/#/features/readme + + +[ref-nestjs]: http://nestjs.com +[ref-mongoose]: https://mongoosejs.com/ +[ref-mongodb]: https://docs.mongodb.com/ +[ref-nodejs-best-practice]: https://github.com/goldbergyoni/nodebestpractices +[ref-nodejs]: https://nodejs.org/ +[ref-typescript]: https://www.typescriptlang.org/ +[ref-jwt]: https://jwt.io +[ref-jest]: https://jestjs.io/docs/getting-started +[ref-docker]: https://docs.docker.com +[ref-docker-compose]: https://docs.docker.com +[ref-yarn]: https://yarnpkg.com +[ref-postman-import-export]: https://learning.postman.com/docs/getting-started/importing-and-exporting-data/ diff --git a/cspell.json b/cspell.json new file mode 100644 index 0000000..a3be45e --- /dev/null +++ b/cspell.json @@ -0,0 +1,41 @@ +{ + "version": "0.2", + "language": "en", + "words": [ + "nestjs", + "metatype", + "virtuals", + "prebuild", + "logform", + "transfomer", + "insync", + "microservices", + "globby", + "dockerhub", + "fifsky", + "buildx", + "exceljs", + "milis", + "workdir", + "dbdata", + "initdb", + "deadcode", + "authapis", + "headerapikey" + ], + "ignoreWords": [ + "qwertyuiop12345zxcvbnmkjh", + "opbUwdiS1FBsrDUoPgZdx", + "cuwakimacojulawu" + ], + "ignorePaths": [ + "node_modules/**", + "endpoints/**", + "*coverage/**", + ".husky/**", + ".github/**", + "dist/**", + "logs/**", + "src/database/json/**" + ] +} \ No newline at end of file diff --git a/lerna.json b/lerna.json new file mode 100644 index 0000000..08ee4e4 --- /dev/null +++ b/lerna.json @@ -0,0 +1,19 @@ +{ + "useWorkspaces": false, + "npmClient": "yarn", + "useNx": false, + "packages": [ + "packages/*" + ], + "npmClientArgs": [], + "changelog": { + "labels": { + "enhancement": "Enhancements", + "bug": "Bug Fixes", + "feat": "Features", + "refactor": "Code Refactors" + }, + "cacheDir": ".changelog" + }, + "version": "0.1.0" +} diff --git a/nx.json b/nx.json new file mode 100644 index 0000000..ec375a4 --- /dev/null +++ b/nx.json @@ -0,0 +1,39 @@ +{ + "extends": "nx/presets/npm.json", + "npmScope": "@sopenapi/monorepo-root", + "affected": { + "defaultBase": "main" + }, + "cli": { + "defaultCollection": "@nrwl/nest" + }, + "implicitDependencies": { + "package.json": { + "dependencies": "*", + "devDependencies": "*" + }, + ".eslintrc.json": "*" + }, + "tasksRunnerOptions": { + "default": { + "runner": "nx/tasks-runners/default", + "options": { + "cacheableOperations": [ + "build", + "lint", + "test", + "e2e" + ] + } + } + }, + "targetDependencies": { + "build": [ + { + "target": "build", + "projects": "dependencies" + } + ] + }, + "defaultProject": "api-auth" +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..2c74dfb --- /dev/null +++ b/package.json @@ -0,0 +1,95 @@ +{ + "name": "@sopenapi/monorepo-root", + "description": "Monorepo root project for a set of Open API MSs", + "version": "0.1.0", + "license": "MIT", + "private": true, + "repository": { + "type": "git", + "url": "git+https://github.com/ja88a/openapi-nestjs-mongo" + }, + "author": { + "name": "Jabba 01", + "email": "dev@srenault.com" + }, + "workspaces": [ + "packages/*" + ], + "main": "dist/index.js", + "types": "types/index.d.ts", + "files": [ + "dist/", + "types/", + "*.md", + "!*.spec.*", + "!**/testdata/" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "lerna run build", + "clean": "rm -rf ./dist ./*.tsbuildinfo", + "reset": "yarn clean && rm -rf ./node_modules ./yarn.lock ./package-lock.json", + "docs": "lerna run docs", + "format": "lerna run format", + "format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\" \"./docs/**/*.md\" \"./scripts/**/*.{json,md}\" && lerna run format-text", + "lint": "lerna run lint", + "lint:fix": "lerna run lint-fix", + "setup": "yarn wsrun -mre -t setup && yarn wsrun -mre -t setup", + "test:unit": "lerna run test:unit", + "test:integration": "lerna run test:integration", + "test:api-auth": "yarn workspace @sopenapi/api-auth test", + "test:example-start-server": "node ./helpers/server.js", + "test": "lerna run test", + "prepare": "lerna run install", + "deadcode": "lerna run deadcode" + }, + "devDependencies": { + "@nestjs/cli": "^8.2.6", + "@nestjs/common": "^8.4.7", + "@nestjs/core": "^8.4.7", + "@nestjs/schematics": "^8.0.11", + "@nestjs/testing": "^8.4.6", + "@types/bcrypt": "^5.0.0", + "@types/compression": "^1.7.2", + "@types/cors": "^2.8.12", + "@types/cron": "^2.0.0", + "@types/crypto-js": "^4.1.1", + "@types/express": "^4.17.13", + "@types/jest": "^28.1.1", + "@types/lodash": "^4.14.182", + "@types/morgan": "^1.9.3", + "@types/multer": "^1.4.7", + "@types/node": "^17.0.40", + "@types/passport-jwt": "^3.0.6", + "@types/supertest": "^2.0.12", + "@types/ua-parser-js": "^0.7.36", + "@types/ws": "^8.5.3", + "@typescript-eslint/eslint-plugin": "^5.27.0", + "@typescript-eslint/parser": "^5.27.0", + "cz-conventional-changelog": "^3.3.0", + "eslint": "^8.17.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "husky": "^8.0.1", + "jest": "^28.1.0", + "lerna": "^5.1.4", + "nx": "^14.3.6", + "prettier": "^2.6.2", + "supertest": "^6.2.3", + "ts-jest": "^28.0.4", + "ts-loader": "^9.3.0", + "ts-node": "^10.8.1", + "ts-prune": "^0.10.3", + "tsconfig-paths": "^4.0.0", + "typescript": "^4.7.3", + "uuid": "^8.3.2", + "webpack": "^5.73.0" + }, + "config": { + "commitizen": { + "path": "./node_modules/cz-conventional-changelog" + } + } +} diff --git a/packages/auth-api/.dockerignore b/packages/auth-api/.dockerignore new file mode 100644 index 0000000..ffb5abf --- /dev/null +++ b/packages/auth-api/.dockerignore @@ -0,0 +1,35 @@ +#husky + +# Build dependencies +node_modules/ +coverage/ +e2e-coverage/ +integration-coverage/ +dist/ +.husky/ +.github/ +prod/ + +# Logs +logs/ + +# Environment (contains sensitive data) +.env* + +# Versioning and metadata +.git +.gitignore +.dockerignore +.eslintignore + +# Files not required for production +.editorconfig +dockerfile +docker-compose.yml +cspell.json +README.md +nodemon.json +scripts/ + +# Misc +*/_* diff --git a/packages/auth-api/.env.docker b/packages/auth-api/.env.docker new file mode 100644 index 0000000..251c10a --- /dev/null +++ b/packages/auth-api/.env.docker @@ -0,0 +1,37 @@ +APP_NAME=sopenapi-auth +APP_ENV=development +APP_MODE=simple +APP_LANGUAGE=en +APP_TZ=Europe/Paris + +APP_HOST=0.0.0.0 +APP_PORT= 3000 +APP_VERSIONING=false +APP_DEBUG=false + +APP_HTTP_ON=true +APP_TASK_ON=false + +DATABASE_HOST=mongodb://mongodb:27017 +DATABASE_NAME=sopenapi +DATABASE_USER=admin +DATABASE_PASSWORD=123456 +DATABASE_DEBUG=false +DATABASE_OPTIONS=authSource=admin + +MIDDLEWARE_TOLERANCE_TIMESTAMP=5m +MIDDLEWARE_TIMEOUT=30s + +AUTH_JWT_ACCESS_TOKEN_SECRET_KEY=123456 +AUTH_JWT_ACCESS_TOKEN_EXPIRED=30m +AUTH_JWT_REFRESH_TOKEN_SECRET_KEY=01001231 +AUTH_JWT_REFRESH_TOKEN_EXPIRED=7d +AUTH_JWT_REFRESH_TOKEN_REMEMBER_ME_EXPIRED=30d + +AUTH_BASIC_TOKEN_CLIENT_ID=asdzxc +AUTH_BASIC_TOKEN_CLIENT_SECRET=1234567890 + +AWS_CREDENTIAL_KEY= +AWS_CREDENTIAL_SECRET= +AWS_S3_REGION=us-east-2 +AWS_S3_BUCKET=jabba01s3 diff --git a/packages/auth-api/.env.example b/packages/auth-api/.env.example new file mode 100644 index 0000000..5d64aad --- /dev/null +++ b/packages/auth-api/.env.example @@ -0,0 +1,37 @@ +APP_NAME=sopenapi-auth +APP_ENV=development +APP_MODE=simple +APP_LANGUAGE=en +APP_TZ=Europe/Paris + +APP_HOST=localhost +APP_PORT= 3000 +APP_VERSIONING=false +APP_DEBUG=false + +APP_HTTP_ON=true +APP_TASK_ON=false + +DATABASE_HOST=mongodb://localhost:27017 +DATABASE_NAME=sopenapi +DATABASE_USER= +DATABASE_PASSWORD= +DATABASE_DEBUG=false +DATABASE_OPTIONS= + +MIDDLEWARE_TOLERANCE_TIMESTAMP=5m +MIDDLEWARE_TIMEOUT=30s + +AUTH_JWT_ACCESS_TOKEN_SECRET_KEY=123456 +AUTH_JWT_ACCESS_TOKEN_EXPIRED=30m +AUTH_JWT_REFRESH_TOKEN_SECRET_KEY=01001231 +AUTH_JWT_REFRESH_TOKEN_EXPIRED=7d +AUTH_JWT_REFRESH_TOKEN_REMEMBER_ME_EXPIRED=30d + +AUTH_BASIC_TOKEN_CLIENT_ID=asdzxc +AUTH_BASIC_TOKEN_CLIENT_SECRET=1234567890 + +AWS_CREDENTIAL_KEY= +AWS_CREDENTIAL_SECRET= +AWS_S3_REGION=us-east-2 +AWS_S3_BUCKET=jabba01s3 \ No newline at end of file diff --git a/packages/auth-api/.env.production b/packages/auth-api/.env.production new file mode 100644 index 0000000..2ee5ddc --- /dev/null +++ b/packages/auth-api/.env.production @@ -0,0 +1,37 @@ +APP_NAME=sopenapi-auth +APP_ENV=production +APP_MODE=secure +APP_LANGUAGE=en +APP_TZ=Europe/Paris + +APP_HOST=localhost +APP_PORT= 3000 +APP_VERSIONING=true +APP_DEBUG=false + +APP_HTTP_ON=true +APP_TASK_ON=false + +DATABASE_HOST=mongodb://mongodb:27017 +DATABASE_NAME=sopenapi +DATABASE_USER= +DATABASE_PASSWORD= +DATABASE_DEBUG=false +DATABASE_OPTIONS= + +MIDDLEWARE_TOLERANCE_TIMESTAMP=5m +MIDDLEWARE_TIMEOUT=30s + +AUTH_JWT_ACCESS_TOKEN_SECRET_KEY=123456 +AUTH_JWT_ACCESS_TOKEN_EXPIRED=30m +AUTH_JWT_REFRESH_TOKEN_SECRET_KEY=01001231 +AUTH_JWT_REFRESH_TOKEN_EXPIRED=7d +AUTH_JWT_REFRESH_TOKEN_REMEMBER_ME_EXPIRED=30d + +AUTH_BASIC_TOKEN_CLIENT_ID=asdzxc +AUTH_BASIC_TOKEN_CLIENT_SECRET=1234567890 + +AWS_CREDENTIAL_KEY= +AWS_CREDENTIAL_SECRET= +AWS_S3_REGION=us-east-2 +AWS_S3_BUCKET=jabba01s3 \ No newline at end of file diff --git a/packages/auth-api/.prettierrc b/packages/auth-api/.prettierrc new file mode 100644 index 0000000..2d6310f --- /dev/null +++ b/packages/auth-api/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "es5", + "tabWidth": 4 +} \ No newline at end of file diff --git a/packages/auth-api/LICENSE.md b/packages/auth-api/LICENSE.md new file mode 100644 index 0000000..8bb030d --- /dev/null +++ b/packages/auth-api/LICENSE.md @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) [2022] [ja88a] +Copyright (c) [2020] [Andrechristikan] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/auth-api/README.md b/packages/auth-api/README.md new file mode 100644 index 0000000..fd01f4e --- /dev/null +++ b/packages/auth-api/README.md @@ -0,0 +1,145 @@ + + +[![NestJs][nestjs-shield]][ref-nestjs] +[![NodeJs][nodejs-shield]][ref-nodejs] +[![Typescript][typescript-shield]][ref-typescript] +[![MongoDB][mongodb-shield]][ref-mongodb] +[![JWT][jwt-shield]][ref-jwt] +[![Jest][jest-shield]][ref-jest] +[![Yarn][yarn-shield]][ref-yarn] +[![Docker][docker-shield]][ref-docker] + +# S*OpenAPI Access Management + +## Purpose + +> API Access Authorization Management: users, roles, server api keys & CORS. + +## Endpoints + +Import [endpoints.json][sopenapi-endpoint] into postman or see our [e2e testing][sopenapi-e2e] + +## Security + +If you change env value of `APP_MODE` to `secure` that will trigger more `Middleware` and `Guard`. + +1. `TimestampMiddleware`, tolerant 5 minutes of request. +2. `UserAgentMiddleware`, whitelist of user agent. +3. `ApiKeyGuard`, check api key based on database. +4. `CorsMiddleware`, check cors based on configs. + +## Features + +- Authentication and Authorization (OAuth2, API Key, Basic Auth, Role Management) +- MongoDB integration using Mongoose +- Database Migration (NestJs-Command) +- Storage management with Amazon (AWS) or maybe with Internal Storage (Fs) +- Server Side Pagination (3 Types) +- Url Versioning +- Request Validation Pipe with Custom Message +- Custom Error Status Code +- Logger (Morgan) and Debugger (Winston) +- Centralize Configuration +- Centralize Exception Filter, and Custom Error Structure +- Multi-language (i18n) +- Timezone Awareness, and Custom Timezone +- Request Timeout, and Custom Timeout (Override) ⌛️ +- Dynamic Setting from Database +- Maintenance Mode on / off +- Cache Manager Implementation, can replace with Redis, Memcached, or anything else +- Support Docker Installation +- Support CI/CD with Github Action or Jenkins +- Husky GitHook For Check Source Code, and Run Test Before Commit +- Linter with EsLint for Typescript + +## Todo + +Next developments: +- [ ] Swagger doc +- [ ] Versioning Serialization (Low Priority) +- [ ] Update Documentation +- [ ] Docker Compose File Mongodb Replication Set (Low Priority) + +## Documentation + +Find more details in the ACK docs: + +- [Documentation][ack-nestjs-mongoose-boilerplate-docs] +- [Example][ack-nestjs-mongoose-boilerplate-docs-example] +- [Tips][ack-nestjs-mongoose-boilerplate-docs-tips] + +## Credits + +This module benefits a lot from the powerful *NestJS* dev framework. It is also largely inspired by the work done by Andre Christi Kan, a.k.a. [ACK](https://github.com/andrechristikan), and the contributors of the template projet [ACK NestJs Boilerplate Mongoose](https://github.com/andrechristikan/ack-nestjs-boilerplate-mongoose) (under MIT license). + +We invite every nodejs/nestjs to benefit from this high-ended boilerplate, in terms of code quality, patterns, tests, deployments & documentations! Thanks for your great contributions the ACK team! + +## License + +Distributed under [MIT license][license]. + + +## Contact + +[Jabba 01][author-email] + +[![Github][github-shield]][author-github] +[![LinkedIn][linkedin-shield]][author-linkedin] + + +[sopenapi-contributors-shield]: https://img.shields.io/github/contributors/ja88a/openapi-nestjs-auth-mongo?style=for-the-badge +[sopenapi-forks-shield]: https://img.shields.io/github/forks/ja88a/openapi-nestjs-auth-mongo?style=for-the-badge +[sopenapi-stars-shield]: https://img.shields.io/github/stars/ja88a/openapi-nestjs-auth-mongo?style=for-the-badge +[sopenapi-issues-shield]: https://img.shields.io/github/issues/ja88a/openapi-nestjs-auth-mongo?style=for-the-badge +[sopenapi-license-shield]: https://img.shields.io/github/license/ja88a/openapi-nestjs-auth-mongo?style=for-the-badge + +[nestjs-shield]: https://img.shields.io/badge/nestjs-%23E0234E.svg?style=for-the-badge&logo=nestjs&logoColor=white +[nodejs-shield]: https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=nodedotjs&logoColor=white +[typescript-shield]: https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white +[mongodb-shield]: https://img.shields.io/badge/MongoDB-white?style=for-the-badge&logo=mongodb&logoColor=4EA94B +[jwt-shield]: https://img.shields.io/badge/JWT-000000?style=for-the-badge&logo=JSON%20web%20tokens&logoColor=white +[jest-shield]: https://img.shields.io/badge/-jest-%23C21325?style=for-the-badge&logo=jest&logoColor=white +[yarn-shield]: https://img.shields.io/badge/yarn-%232C8EBB.svg?style=for-the-badge&logo=yarn&logoColor=white +[docker-shield]: https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white + +[github-shield]: https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white +[linkedin-shield]: https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white + + +[author-linkedin]: https://linkedin.com/in/srenault +[author-email]: mailto:support@srenault.com +[author-github]: https://github.com/ja88a + + +[sopenapi-e2e]: /e2e +[sopenapi-endpoint]: /endpoints/endpoints.json + + +[license]: LICENSE.md +[endpoints]: endpoints.json + + +[ack-nestjs-mongoose-boilerplate-docs]: https://andrechristikan.github.io/ack-nestjs-boilerplate-docs/ +[ack-nestjs-mongoose-boilerplate-docs-features]: https://andrechristikan.github.io/ack-nestjs-boilerplate-docs/#/features/readme +[ack-nestjs-mongoose-boilerplate-docs-example]: https://andrechristikan.github.io/ack-nestjs-boilerplate-docs/#/example +[ack-nestjs-mongoose-boilerplate-docs-tips]: https://andrechristikan.github.io/ack-nestjs-boilerplate-docs/#/tips/readme +[ack-nestjs-mongoose-boilerplate-docs-env]: https://andrechristikan.github.io/ack-nestjs-boilerplate-docs/#/features/readme + + +[ref-nestjs]: http://nestjs.com +[ref-mongoose]: https://mongoosejs.com/ +[ref-mongodb]: https://docs.mongodb.com/ +[ref-nodejs-best-practice]: https://github.com/goldbergyoni/nodebestpractices +[ref-nodejs]: https://nodejs.org/ +[ref-typescript]: https://www.typescriptlang.org/ +[ref-jwt]: https://jwt.io +[ref-jest]: https://jestjs.io/docs/getting-started +[ref-docker]: https://docs.docker.com +[ref-yarn]: https://yarnpkg.com +[ref-postman-import-export]: https://learning.postman.com/docs/getting-started/importing-and-exporting-data/ diff --git a/packages/auth-api/cspell.json b/packages/auth-api/cspell.json new file mode 100644 index 0000000..a3be45e --- /dev/null +++ b/packages/auth-api/cspell.json @@ -0,0 +1,41 @@ +{ + "version": "0.2", + "language": "en", + "words": [ + "nestjs", + "metatype", + "virtuals", + "prebuild", + "logform", + "transfomer", + "insync", + "microservices", + "globby", + "dockerhub", + "fifsky", + "buildx", + "exceljs", + "milis", + "workdir", + "dbdata", + "initdb", + "deadcode", + "authapis", + "headerapikey" + ], + "ignoreWords": [ + "qwertyuiop12345zxcvbnmkjh", + "opbUwdiS1FBsrDUoPgZdx", + "cuwakimacojulawu" + ], + "ignorePaths": [ + "node_modules/**", + "endpoints/**", + "*coverage/**", + ".husky/**", + ".github/**", + "dist/**", + "logs/**", + "src/database/json/**" + ] +} \ No newline at end of file diff --git a/packages/auth-api/docker-compose.yml b/packages/auth-api/docker-compose.yml new file mode 100644 index 0000000..ac4e696 --- /dev/null +++ b/packages/auth-api/docker-compose.yml @@ -0,0 +1,46 @@ +version: '3.8' +services: + service: + build: . + container_name: sopenapi-auth + hostname: sopenapi-auth + ports: + - 3000:3000 + networks: + - app-network + volumes: + - ./src/:/app/src/ + - .env/:/app/.env + restart: unless-stopped + depends_on: + - redis + - mongodb + mongodb: + image: mongo:5.0.6 + container_name: mongodb + hostname: mongodb + ports: + - 27017:27017 + environment: + MONGO_INITDB_ROOT_USERNAME: admin + MONGO_INITDB_ROOT_PASSWORD: 123456 + MONGO_INITDB_DATABASE: sopenapi + volumes: + - dbdata:/data/db + restart: unless-stopped + networks: + - app-network + redis: + image: redis:6.2.6 + container_name: redis + hostname: redis + ports: + - '6379:6379' + networks: + - app-network +networks: + app-network: + name: app-network + driver: bridge +volumes: + dbdata: \ No newline at end of file diff --git a/packages/auth-api/dockerfile b/packages/auth-api/dockerfile new file mode 100644 index 0000000..ecb31e3 --- /dev/null +++ b/packages/auth-api/dockerfile @@ -0,0 +1,15 @@ +FROM node:lts-alpine +LABEL maintainer "r0g3r@tuta.io" + +WORKDIR /app +EXPOSE 3000 + +COPY package.json yarn.lock ./ +RUN touch .env + +RUN set -x && yarn +RUN yarn global add @nestjs/cli + +COPY . . + +CMD [ "yarn", "start" ] \ No newline at end of file diff --git a/packages/auth-api/e2e/auth/auth.change-password.e2e-spec.ts b/packages/auth-api/e2e/auth/auth.change-password.e2e-spec.ts new file mode 100644 index 0000000..67eb5c6 --- /dev/null +++ b/packages/auth-api/e2e/auth/auth.change-password.e2e-spec.ts @@ -0,0 +1,226 @@ +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import request from 'supertest'; +import { faker } from '@faker-js/faker'; +import { IUserDocument } from 'src/user/user.interface'; +import { E2E_AUTH_CHANGE_PASSWORD_URL } from './auth.constant'; +import { ENUM_USER_STATUS_CODE_ERROR } from 'src/user/user.constant'; +import { Types, connection } from 'mongoose'; +import { ENUM_AUTH_STATUS_CODE_ERROR } from 'src/auth/auth.constant'; +import { CoreModule } from 'src/core/core.module'; +import { RouterModule } from '@nestjs/core'; +import { UserService } from 'src/user/service/user.service'; +import { AuthService } from 'src/auth/service/auth.service'; +import { RoleService } from 'src/role/service/role.service'; +import { ENUM_REQUEST_STATUS_CODE_ERROR } from 'src/utils/request/request.constant'; +import { RouterCommonModule } from 'src/router/router.common.module'; +import { UserDocument } from 'src/user/schema/user.schema'; +import { RoleDocument } from 'src/role/schema/role.schema'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { useContainer } from 'class-validator'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; + +describe('E2E Change Password', () => { + let app: INestApplication; + let userService: UserService; + let authService: AuthService; + let authApiService: AuthApiService; + let roleService: RoleService; + let helperDateService: HelperDateService; + + const apiKey = 'qwertyuiop12345zxcvbnmkjh'; + let xApiKey: string; + let timestamp: number; + + const password = `aaAA@!123`; + const newPassword = `bbBB@!456`; + + let user: UserDocument; + + let accessToken: string; + let accessTokenNotFound: string; + + beforeAll(async () => { + const modRef = await Test.createTestingModule({ + imports: [ + CoreModule, + RouterCommonModule, + RouterModule.register([ + { + path: '/', + module: RouterCommonModule, + }, + ]), + ], + }).compile(); + + app = modRef.createNestApplication(); + useContainer(app.select(CoreModule), { fallbackOnErrors: true }); + userService = app.get(UserService); + authService = app.get(AuthService); + authApiService = app.get(AuthApiService); + roleService = app.get(RoleService); + helperDateService = app.get(HelperDateService); + + const role: RoleDocument = await roleService.findOne({ + name: 'user', + }); + + const passwordHash = await authService.createPassword(password); + + user = await userService.create({ + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + password: passwordHash.passwordHash, + passwordExpired: passwordHash.passwordExpired, + salt: passwordHash.salt, + email: faker.internet.email(), + mobileNumber: faker.phone.number('62812#########'), + role: `${role._id}`, + }); + + const userPopulate = await userService.findOneById( + user._id, + { + populate: { + role: true, + permission: true, + }, + } + ); + + timestamp = helperDateService.timestamp(); + const apiEncryption = await authApiService.encryptApiKey( + { + key: apiKey, + timestamp, + hash: 'e11a023bc0ccf713cb50de9baa5140e59d3d4c52ec8952d9ca60326e040eda54', + }, + 'opbUwdiS1FBsrDUoPgZdx', + 'cuwakimacojulawu' + ); + xApiKey = `${apiKey}:${apiEncryption}`; + + const map = await authService.serializationLogin(userPopulate); + const payload = await authService.createPayloadAccessToken(map, false); + const payloadNotFound = { + ...payload, + _id: `${new Types.ObjectId()}`, + }; + + accessToken = await authService.createAccessToken(payload); + accessTokenNotFound = await authService.createAccessToken( + payloadNotFound + ); + await app.init(); + }); + + it(`PATCH ${E2E_AUTH_CHANGE_PASSWORD_URL} Error Request`, async () => { + const response = await request(app.getHttpServer()) + .patch(E2E_AUTH_CHANGE_PASSWORD_URL) + .send({ + oldPassword: '123123', + newPassword: '123', + }) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.UNPROCESSABLE_ENTITY); + expect(response.body.statusCode).toEqual( + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_VALIDATION_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_AUTH_CHANGE_PASSWORD_URL} Not Found`, async () => { + const response = await request(app.getHttpServer()) + .patch(E2E_AUTH_CHANGE_PASSWORD_URL) + .send({ + oldPassword: password, + newPassword, + }) + .set('Authorization', `Bearer ${accessTokenNotFound}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_AUTH_CHANGE_PASSWORD_URL} Old Password Not Match`, async () => { + const response = await request(app.getHttpServer()) + .patch(E2E_AUTH_CHANGE_PASSWORD_URL) + .send({ + oldPassword: 'as1231dAA@@!', + newPassword, + }) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_PASSWORD_NOT_MATCH_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_AUTH_CHANGE_PASSWORD_URL} New Password must different with old password`, async () => { + const response = await request(app.getHttpServer()) + .patch(E2E_AUTH_CHANGE_PASSWORD_URL) + .send({ + oldPassword: password, + newPassword: password, + }) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_PASSWORD_NEW_MUST_DIFFERENCE_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_AUTH_CHANGE_PASSWORD_URL} Success`, async () => { + const response = await request(app.getHttpServer()) + .patch(E2E_AUTH_CHANGE_PASSWORD_URL) + .send({ + oldPassword: password, + newPassword, + }) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + afterAll(async () => { + try { + await userService.deleteOneById(user._id); + } catch (e) { + console.error(e); + } + + connection.close(); + await app.close(); + }); +}); diff --git a/packages/auth-api/e2e/auth/auth.constant.ts b/packages/auth-api/e2e/auth/auth.constant.ts new file mode 100644 index 0000000..21c15e8 --- /dev/null +++ b/packages/auth-api/e2e/auth/auth.constant.ts @@ -0,0 +1,5 @@ +export const E2E_AUTH_LOGIN_URL = '/auth/login'; +export const E2E_AUTH_REFRESH_URL = '/auth/refresh'; +export const E2E_AUTH_CHANGE_PASSWORD_URL = '/auth/change-password'; + +export const E2E_AUTH_PUBLIC_SIGN_UP_URL = '/public/auth/sign-up'; diff --git a/packages/auth-api/e2e/auth/auth.login.e2e-spec.ts b/packages/auth-api/e2e/auth/auth.login.e2e-spec.ts new file mode 100644 index 0000000..645cfe8 --- /dev/null +++ b/packages/auth-api/e2e/auth/auth.login.e2e-spec.ts @@ -0,0 +1,266 @@ +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import request from 'supertest'; +import { faker } from '@faker-js/faker'; +import { E2E_AUTH_LOGIN_URL } from './auth.constant'; +import { UserDocument } from 'src/user/schema/user.schema'; +import { RoleDocument } from 'src/role/schema/role.schema'; +import { ENUM_USER_STATUS_CODE_ERROR } from 'src/user/user.constant'; +import { + ENUM_AUTH_STATUS_CODE_ERROR, + ENUM_AUTH_STATUS_CODE_SUCCESS, +} from 'src/auth/auth.constant'; +import { ENUM_ROLE_STATUS_CODE_ERROR } from 'src/role/role.constant'; +import { CoreModule } from 'src/core/core.module'; +import { RouterModule } from '@nestjs/core'; +import { connection } from 'mongoose'; +import { RoleService } from 'src/role/service/role.service'; +import { UserService } from 'src/user/service/user.service'; +import { AuthService } from 'src/auth/service/auth.service'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { ENUM_REQUEST_STATUS_CODE_ERROR } from 'src/utils/request/request.constant'; +import { RouterCommonModule } from 'src/router/router.common.module'; +import { useContainer } from 'class-validator'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; + +describe('E2E Login', () => { + let app: INestApplication; + let userService: UserService; + let authService: AuthService; + let roleService: RoleService; + let helperDateService: HelperDateService; + let authApiService: AuthApiService; + + const password = `@!${faker.name.firstName().toLowerCase()}${faker.name + .firstName() + .toUpperCase()}${faker.datatype.number({ min: 1, max: 99 })}`; + + const apiKey = 'qwertyuiop12345zxcvbnmkjh'; + let xApiKey: string; + let timestamp: number; + + let user: UserDocument; + + let passwordExpired: Date; + + beforeAll(async () => { + const modRef = await Test.createTestingModule({ + imports: [ + CoreModule, + RouterCommonModule, + RouterModule.register([ + { + path: '/', + module: RouterCommonModule, + }, + ]), + ], + }).compile(); + + app = modRef.createNestApplication(); + useContainer(app.select(CoreModule), { fallbackOnErrors: true }); + userService = app.get(UserService); + authService = app.get(AuthService); + roleService = app.get(RoleService); + helperDateService = app.get(HelperDateService); + authApiService = app.get(AuthApiService); + + const role: RoleDocument = await roleService.findOne({ + name: 'user', + }); + + passwordExpired = helperDateService.backwardInDays(5); + + const passwordHash = await authService.createPassword(password); + + user = await userService.create({ + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + password: passwordHash.passwordHash, + passwordExpired: passwordHash.passwordExpired, + salt: passwordHash.salt, + email: faker.internet.email(), + mobileNumber: faker.phone.number('62812#########'), + role: `${role._id}`, + }); + + timestamp = helperDateService.timestamp(); + const apiEncryption = await authApiService.encryptApiKey( + { + key: apiKey, + timestamp, + hash: 'e11a023bc0ccf713cb50de9baa5140e59d3d4c52ec8952d9ca60326e040eda54', + }, + 'opbUwdiS1FBsrDUoPgZdx', + 'cuwakimacojulawu' + ); + xApiKey = `${apiKey}:${apiEncryption}`; + + await app.init(); + }); + + it(`POST ${E2E_AUTH_LOGIN_URL} Error Request`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_LOGIN_URL) + .set('Content-Type', 'application/json') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + email: [1231], + password, + rememberMe: false, + }); + + expect(response.status).toEqual(HttpStatus.UNPROCESSABLE_ENTITY); + expect(response.body.statusCode).toEqual( + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_VALIDATION_ERROR + ); + + return; + }); + + it(`POST ${E2E_AUTH_LOGIN_URL} Not Found`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_LOGIN_URL) + .set('Content-Type', 'application/json') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + email: faker.internet.email(), + password, + rememberMe: false, + }); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR + ); + + return; + }); + + it(`POST ${E2E_AUTH_LOGIN_URL} Password Not Match`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_LOGIN_URL) + .set('Content-Type', 'application/json') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + email: user.email, + password: 'Password@@1231', + rememberMe: false, + }); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_PASSWORD_NOT_MATCH_ERROR + ); + + return; + }); + + it(`POST ${E2E_AUTH_LOGIN_URL} Inactive`, async () => { + await userService.inactive(user._id); + + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_LOGIN_URL) + .set('Content-Type', 'application/json') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + email: user.email, + password, + rememberMe: false, + }); + + await userService.active(user._id); + expect(response.status).toEqual(HttpStatus.FORBIDDEN); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_IS_INACTIVE_ERROR + ); + + return; + }); + + it(`POST ${E2E_AUTH_LOGIN_URL} Role Inactive`, async () => { + await roleService.inactive(`${user.role}`); + + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_LOGIN_URL) + .set('Content-Type', 'application/json') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + email: user.email, + password, + rememberMe: false, + }); + + await roleService.active(`${user.role}`); + expect(response.status).toEqual(HttpStatus.FORBIDDEN); + expect(response.body.statusCode).toEqual( + ENUM_ROLE_STATUS_CODE_ERROR.ROLE_IS_INACTIVE_ERROR + ); + + return; + }); + + it(`POST ${E2E_AUTH_LOGIN_URL} Success`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_LOGIN_URL) + .set('Content-Type', 'application/json') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + email: user.email, + password, + rememberMe: false, + }); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual( + ENUM_AUTH_STATUS_CODE_SUCCESS.AUTH_LOGIN_SUCCESS + ); + + return; + }); + + it(`POST ${E2E_AUTH_LOGIN_URL} Password Expired`, async () => { + await userService.updatePasswordExpired(user._id, passwordExpired); + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_LOGIN_URL) + .set('Content-Type', 'application/json') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + email: user.email, + password, + rememberMe: false, + }); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual( + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_PASSWORD_EXPIRED_ERROR + ); + + return; + }); + + afterAll(async () => { + try { + await userService.deleteOneById(user._id); + } catch (e) { + console.error(e); + } + + connection.close(); + await app.close(); + }); +}); diff --git a/packages/auth-api/e2e/auth/auth.public.e2e-spec.ts b/packages/auth-api/e2e/auth/auth.public.e2e-spec.ts new file mode 100644 index 0000000..214a32e --- /dev/null +++ b/packages/auth-api/e2e/auth/auth.public.e2e-spec.ts @@ -0,0 +1,178 @@ +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import request from 'supertest'; +import { faker } from '@faker-js/faker'; +import { E2E_AUTH_PUBLIC_SIGN_UP_URL } from './auth.constant'; +import { ENUM_USER_STATUS_CODE_ERROR } from 'src/user/user.constant'; +import { CoreModule } from 'src/core/core.module'; +import { RouterModule } from '@nestjs/core'; +import { connection } from 'mongoose'; +import { UserService } from 'src/user/service/user.service'; +import { ENUM_REQUEST_STATUS_CODE_ERROR } from 'src/utils/request/request.constant'; +import { RouterPublicModule } from 'src/router/router.public.module'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { useContainer } from 'class-validator'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; + +describe('E2E Public', () => { + let app: INestApplication; + let userService: UserService; + let helperDateService: HelperDateService; + let authApiService: AuthApiService; + + const password = `@!aaAA@123`; + + const apiKey = 'qwertyuiop12345zxcvbnmkjh'; + let xApiKey: string; + let timestamp: number; + + let userData: Record; + + beforeAll(async () => { + const modRef = await Test.createTestingModule({ + imports: [ + CoreModule, + RouterPublicModule, + RouterModule.register([ + { + path: '/public', + module: RouterPublicModule, + }, + ]), + ], + }).compile(); + + app = modRef.createNestApplication(); + useContainer(app.select(CoreModule), { fallbackOnErrors: true }); + userService = app.get(UserService); + helperDateService = app.get(HelperDateService); + authApiService = app.get(AuthApiService); + + userData = { + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + password: password, + email: faker.internet.email(), + mobileNumber: faker.phone.number('62812#########'), + }; + + timestamp = helperDateService.timestamp(); + const apiEncryption = await authApiService.encryptApiKey( + { + key: apiKey, + timestamp, + hash: 'e11a023bc0ccf713cb50de9baa5140e59d3d4c52ec8952d9ca60326e040eda54', + }, + 'opbUwdiS1FBsrDUoPgZdx', + 'cuwakimacojulawu' + ); + xApiKey = `${apiKey}:${apiEncryption}`; + + await app.init(); + }); + + it(`POST ${E2E_AUTH_PUBLIC_SIGN_UP_URL} Sign Up Error Request`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_PUBLIC_SIGN_UP_URL) + .set('Content-Type', 'application/json') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + email: faker.name.firstName().toLowerCase(), + firstName: faker.name.firstName().toLowerCase(), + lastName: faker.name.lastName().toLowerCase(), + }); + + expect(response.status).toEqual(HttpStatus.UNPROCESSABLE_ENTITY); + expect(response.body.statusCode).toEqual( + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_VALIDATION_ERROR + ); + + return; + }); + + it(`POST ${E2E_AUTH_PUBLIC_SIGN_UP_URL} Sign Up Success`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_PUBLIC_SIGN_UP_URL) + .set('Content-Type', 'application/json') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send(userData); + + expect(response.status).toEqual(HttpStatus.CREATED); + expect(response.body.statusCode).toEqual(HttpStatus.CREATED); + }); + + it(`POST ${E2E_AUTH_PUBLIC_SIGN_UP_URL} Sign Up Exist`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_PUBLIC_SIGN_UP_URL) + .set('Content-Type', 'application/json') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send(userData); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_EXISTS_ERROR + ); + + return; + }); + + it(`POST ${E2E_AUTH_PUBLIC_SIGN_UP_URL} Sign Up Email Exist`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_PUBLIC_SIGN_UP_URL) + .set('Content-Type', 'application/json') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + ...userData, + mobileNumber: faker.phone.number('62812#########'), + }); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_EMAIL_EXIST_ERROR + ); + + return; + }); + + it(`POST ${E2E_AUTH_PUBLIC_SIGN_UP_URL} Sign Up Mobile Number Exist`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_PUBLIC_SIGN_UP_URL) + .set('Content-Type', 'application/json') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + ...userData, + email: faker.internet.email(), + }); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_MOBILE_NUMBER_EXIST_ERROR + ); + + return; + }); + + afterAll(async () => { + try { + await userService.deleteOne({ + email: userData.email, + mobileNumber: userData.mobileNumber, + }); + } catch (e) { + console.error(e); + } + + connection.close(); + await app.close(); + }); +}); diff --git a/packages/auth-api/e2e/auth/auth.refresh.e2e-spec.ts b/packages/auth-api/e2e/auth/auth.refresh.e2e-spec.ts new file mode 100644 index 0000000..eda4121 --- /dev/null +++ b/packages/auth-api/e2e/auth/auth.refresh.e2e-spec.ts @@ -0,0 +1,228 @@ +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import request from 'supertest'; +import { faker } from '@faker-js/faker'; +import { E2E_AUTH_REFRESH_URL } from './auth.constant'; +import { ENUM_USER_STATUS_CODE_ERROR } from 'src/user/user.constant'; +import { UserDocument } from 'src/user/schema/user.schema'; +import { RoleDocument } from 'src/role/schema/role.schema'; +import { ENUM_AUTH_STATUS_CODE_ERROR } from 'src/auth/auth.constant'; +import { ENUM_ROLE_STATUS_CODE_ERROR } from 'src/role/role.constant'; +import { IUserDocument } from 'src/user/user.interface'; +import { Types, connection } from 'mongoose'; +import { CoreModule } from 'src/core/core.module'; +import { RouterModule } from '@nestjs/core'; +import { UserService } from 'src/user/service/user.service'; +import { AuthService } from 'src/auth/service/auth.service'; +import { RoleService } from 'src/role/service/role.service'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { RouterCommonModule } from 'src/router/router.common.module'; +import { useContainer } from 'class-validator'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; + +describe('E2E Refresh', () => { + let app: INestApplication; + let userService: UserService; + let authService: AuthService; + let roleService: RoleService; + let helperDateService: HelperDateService; + let authApiService: AuthApiService; + + const password = `@!${faker.name.firstName().toLowerCase()}${faker.name + .firstName() + .toUpperCase()}${faker.datatype.number({ min: 1, max: 99 })}`; + + const apiKey = 'qwertyuiop12345zxcvbnmkjh'; + let xApiKey: string; + let timestamp: number; + + let user: UserDocument; + let passwordExpired: Date; + let passwordExpiredForward: Date; + + let refreshToken: string; + let refreshTokenNotFound: string; + + beforeAll(async () => { + const modRef = await Test.createTestingModule({ + imports: [ + CoreModule, + RouterCommonModule, + RouterModule.register([ + { + path: '/', + module: RouterCommonModule, + }, + ]), + ], + }).compile(); + + app = modRef.createNestApplication(); + useContainer(app.select(CoreModule), { fallbackOnErrors: true }); + userService = app.get(UserService); + authService = app.get(AuthService); + roleService = app.get(RoleService); + helperDateService = app.get(HelperDateService); + authApiService = app.get(AuthApiService); + + const role: RoleDocument = await roleService.findOne({ + name: 'user', + }); + + passwordExpired = helperDateService.backwardInDays(5); + passwordExpiredForward = helperDateService.forwardInDays(5); + + const passwordHash = await authService.createPassword(password); + + user = await userService.create({ + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + password: passwordHash.passwordHash, + passwordExpired: passwordHash.passwordExpired, + salt: passwordHash.salt, + email: faker.internet.email(), + mobileNumber: faker.phone.number('62812#########'), + role: `${role._id}`, + }); + + const userPopulate = await userService.findOneById( + user._id, + { + populate: { + role: true, + permission: true, + }, + } + ); + + const map = await authService.serializationLogin(userPopulate); + const payload = await authService.createPayloadRefreshToken(map, false); + const payloadNotFound = { + ...payload, + _id: `${new Types.ObjectId()}`, + }; + + refreshToken = await authService.createRefreshToken( + payload, + false, + true + ); + refreshTokenNotFound = await authService.createRefreshToken( + payloadNotFound, + false, + true + ); + + timestamp = helperDateService.timestamp(); + const apiEncryption = await authApiService.encryptApiKey( + { + key: apiKey, + timestamp, + hash: 'e11a023bc0ccf713cb50de9baa5140e59d3d4c52ec8952d9ca60326e040eda54', + }, + 'opbUwdiS1FBsrDUoPgZdx', + 'cuwakimacojulawu' + ); + xApiKey = `${apiKey}:${apiEncryption}`; + + await app.init(); + }); + + it(`POST ${E2E_AUTH_REFRESH_URL} Not Found`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_REFRESH_URL) + .set('Authorization', `Bearer ${refreshTokenNotFound}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR + ); + + return; + }); + + it(`POST ${E2E_AUTH_REFRESH_URL} Inactive`, async () => { + await userService.inactive(user._id); + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_REFRESH_URL) + .set('Authorization', `Bearer ${refreshToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + await userService.active(user._id); + expect(response.status).toEqual(HttpStatus.FORBIDDEN); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_IS_INACTIVE_ERROR + ); + + return; + }); + + it(`POST ${E2E_AUTH_REFRESH_URL} Role Inactive`, async () => { + await roleService.inactive(`${user.role}`); + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_REFRESH_URL) + .set('Authorization', `Bearer ${refreshToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + await roleService.active(`${user.role}`); + expect(response.status).toEqual(HttpStatus.FORBIDDEN); + expect(response.body.statusCode).toEqual( + ENUM_ROLE_STATUS_CODE_ERROR.ROLE_IS_INACTIVE_ERROR + ); + + return; + }); + + it(`POST ${E2E_AUTH_REFRESH_URL} Password Expired`, async () => { + await userService.updatePasswordExpired(user._id, passwordExpired); + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_REFRESH_URL) + .set('Authorization', `Bearer ${refreshToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + await userService.updatePasswordExpired( + user._id, + passwordExpiredForward + ); + expect(response.status).toEqual(HttpStatus.FORBIDDEN); + expect(response.body.statusCode).toEqual( + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_PASSWORD_EXPIRED_ERROR + ); + + return; + }); + + it(`POST ${E2E_AUTH_REFRESH_URL} Success`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_AUTH_REFRESH_URL) + .set('Authorization', `Bearer ${refreshToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + afterAll(async () => { + try { + await userService.deleteOneById(user._id); + } catch (e) { + console.error(e); + } + + connection.close(); + await app.close(); + }); +}); diff --git a/packages/auth-api/e2e/jest.json b/packages/auth-api/e2e/jest.json new file mode 100644 index 0000000..b17d10c --- /dev/null +++ b/packages/auth-api/e2e/jest.json @@ -0,0 +1,32 @@ +{ + "testTimeout": 10000, + "rootDir": "..", + "modulePaths": [ + "." + ], + "testEnvironment": "node", + "testMatch": [ + "/e2e/**/*.e2e-spec.ts" + ], + "collectCoverage": true, + "coverageDirectory": "e2e-coverage", + "collectCoverageFrom": [ + "./e2e" + ], + "coverageThreshold": { + "global": { + "branches": 90, + "functions": 90, + "lines": 90, + "statements": 90 + } + }, + "moduleFileExtensions": [ + "js", + "ts", + "json" + ], + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/packages/auth-api/e2e/permission/permission.admin.e2e-spec.ts b/packages/auth-api/e2e/permission/permission.admin.e2e-spec.ts new file mode 100644 index 0000000..b9c9a60 --- /dev/null +++ b/packages/auth-api/e2e/permission/permission.admin.e2e-spec.ts @@ -0,0 +1,332 @@ +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import request from 'supertest'; +import { faker } from '@faker-js/faker'; +import { + E2E_PERMISSION_ADMIN_ACTIVE_URL, + E2E_PERMISSION_ADMIN_GET_URL, + E2E_PERMISSION_ADMIN_INACTIVE_URL, + E2E_PERMISSION_ADMIN_LIST_URL, + E2E_PERMISSION_ADMIN_UPDATE_URL, + E2E_PERMISSION_PAYLOAD_TEST, +} from './permission.constant'; +import { Types, connection } from 'mongoose'; +import { ENUM_PERMISSION_STATUS_CODE_ERROR } from 'src/permission/permission.constant'; +import { CoreModule } from 'src/core/core.module'; +import { RouterModule } from '@nestjs/core'; +import { PermissionService } from 'src/permission/service/permission.service'; +import { AuthService } from 'src/auth/service/auth.service'; +import { ENUM_REQUEST_STATUS_CODE_ERROR } from 'src/utils/request/request.constant'; +import { RouterAdminModule } from 'src/router/router.admin.module'; +import { PermissionDocument } from 'src/permission/schema/permission.schema'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { PermissionUpdateDto } from 'src/permission/dto/permission.update.dto'; +import { useContainer } from 'class-validator'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; + +describe('E2E Permission Admin', () => { + let app: INestApplication; + let authService: AuthService; + let permissionService: PermissionService; + let helperDateService: HelperDateService; + let authApiService: AuthApiService; + + let accessToken: string; + let permission: PermissionDocument; + + const updateData: PermissionUpdateDto = { + name: 'update role', + description: 'UPDATE_ROLE', + }; + + const apiKey = 'qwertyuiop12345zxcvbnmkjh'; + let xApiKey: string; + let timestamp: number; + + beforeAll(async () => { + const modRef = await Test.createTestingModule({ + imports: [ + CoreModule, + RouterAdminModule, + RouterModule.register([ + { + path: '/admin', + module: RouterAdminModule, + }, + ]), + ], + }).compile(); + + app = modRef.createNestApplication(); + useContainer(app.select(CoreModule), { fallbackOnErrors: true }); + authService = app.get(AuthService); + permissionService = app.get(PermissionService); + helperDateService = app.get(HelperDateService); + authApiService = app.get(AuthApiService); + + accessToken = await authService.createAccessToken({ + ...E2E_PERMISSION_PAYLOAD_TEST, + loginDate: new Date(), + rememberMe: false, + }); + + permission = await permissionService.create({ + isActive: true, + name: 'testPermission', + code: 'TEST_PERMISSION_XXXX', + description: 'test description', + }); + + timestamp = helperDateService.timestamp(); + const apiEncryption = await authApiService.encryptApiKey( + { + key: apiKey, + timestamp, + hash: 'e11a023bc0ccf713cb50de9baa5140e59d3d4c52ec8952d9ca60326e040eda54', + }, + 'opbUwdiS1FBsrDUoPgZdx', + 'cuwakimacojulawu' + ); + xApiKey = `${apiKey}:${apiEncryption}`; + + await app.init(); + }); + + it(`GET ${E2E_PERMISSION_ADMIN_LIST_URL} List Success`, async () => { + const response = await request(app.getHttpServer()) + .get(E2E_PERMISSION_ADMIN_LIST_URL) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`GET ${E2E_PERMISSION_ADMIN_GET_URL} Get Not Found`, async () => { + const response = await request(app.getHttpServer()) + .get( + E2E_PERMISSION_ADMIN_GET_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_PERMISSION_STATUS_CODE_ERROR.PERMISSION_NOT_FOUND_ERROR + ); + + return; + }); + + it(`GET ${E2E_PERMISSION_ADMIN_GET_URL} Get Success`, async () => { + const response = await request(app.getHttpServer()) + .get(E2E_PERMISSION_ADMIN_GET_URL.replace(':_id', permission._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`PUT ${E2E_PERMISSION_ADMIN_UPDATE_URL} Update Not found`, async () => { + const response = await request(app.getHttpServer()) + .put( + E2E_PERMISSION_ADMIN_UPDATE_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .send(updateData) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_PERMISSION_STATUS_CODE_ERROR.PERMISSION_NOT_FOUND_ERROR + ); + + return; + }); + + it(`PUT ${E2E_PERMISSION_ADMIN_UPDATE_URL} Update Error Request`, async () => { + const response = await request(app.getHttpServer()) + .put( + E2E_PERMISSION_ADMIN_UPDATE_URL.replace(':_id', permission._id) + ) + .send({ + name: [1231231], + }) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.UNPROCESSABLE_ENTITY); + expect(response.body.statusCode).toEqual( + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_VALIDATION_ERROR + ); + + return; + }); + + it(`PUT ${E2E_PERMISSION_ADMIN_UPDATE_URL} Update Success`, async () => { + const response = await request(app.getHttpServer()) + .put( + E2E_PERMISSION_ADMIN_UPDATE_URL.replace(':_id', permission._id) + ) + .send(updateData) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`PATCH ${E2E_PERMISSION_ADMIN_ACTIVE_URL} Active not found`, async () => { + const response = await request(app.getHttpServer()) + .patch( + E2E_PERMISSION_ADMIN_ACTIVE_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_PERMISSION_STATUS_CODE_ERROR.PERMISSION_NOT_FOUND_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_PERMISSION_ADMIN_ACTIVE_URL} Active already Active`, async () => { + const response = await request(app.getHttpServer()) + .patch( + E2E_PERMISSION_ADMIN_ACTIVE_URL.replace(':_id', permission._id) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_PERMISSION_STATUS_CODE_ERROR.PERMISSION_ACTIVE_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_PERMISSION_ADMIN_INACTIVE_URL} Inactive not found`, async () => { + const response = await request(app.getHttpServer()) + .patch( + E2E_PERMISSION_ADMIN_INACTIVE_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_PERMISSION_STATUS_CODE_ERROR.PERMISSION_NOT_FOUND_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_PERMISSION_ADMIN_INACTIVE_URL} Inactive Success`, async () => { + const response = await request(app.getHttpServer()) + .patch( + E2E_PERMISSION_ADMIN_INACTIVE_URL.replace( + ':_id', + permission._id + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`PATCH ${E2E_PERMISSION_ADMIN_INACTIVE_URL} Inactive already inactive`, async () => { + const response = await request(app.getHttpServer()) + .patch( + E2E_PERMISSION_ADMIN_INACTIVE_URL.replace( + ':_id', + permission._id + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_PERMISSION_STATUS_CODE_ERROR.PERMISSION_ACTIVE_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_PERMISSION_ADMIN_ACTIVE_URL} Active Success`, async () => { + const response = await request(app.getHttpServer()) + .patch( + E2E_PERMISSION_ADMIN_ACTIVE_URL.replace(':_id', permission._id) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + afterAll(async () => { + try { + await permission.deleteOne({ + _id: new Types.ObjectId(permission._id), + }); + } catch (e) { + console.error(e); + } + + connection.close(); + await app.close(); + }); +}); diff --git a/packages/auth-api/e2e/permission/permission.constant.ts b/packages/auth-api/e2e/permission/permission.constant.ts new file mode 100644 index 0000000..d0b5968 --- /dev/null +++ b/packages/auth-api/e2e/permission/permission.constant.ts @@ -0,0 +1,54 @@ +export const E2E_PERMISSION_ADMIN_LIST_URL = '/admin/permission/list'; +export const E2E_PERMISSION_ADMIN_GET_URL = '/admin/permission/get/:_id'; +export const E2E_PERMISSION_ADMIN_UPDATE_URL = '/admin/permission/update/:_id'; +export const E2E_PERMISSION_ADMIN_ACTIVE_URL = + '/admin/permission/update/:_id/active'; +export const E2E_PERMISSION_ADMIN_INACTIVE_URL = + '/admin/permission/update/:_id/inactive'; + +export const E2E_PERMISSION_PAYLOAD_TEST = { + role: { + name: 'admin', + isActive: true, + isAdmin: true, + permissions: [ + { + code: 'PERMISSION_READ', + isActive: true, + }, + { + code: 'PERMISSION_UPDATE', + isActive: true, + }, + { + code: 'PERMISSION_DELETE', + isActive: true, + }, + { + code: 'PERMISSION_CREATE', + isActive: true, + }, + { + code: 'PERMISSION_INACTIVE', + isActive: true, + }, + { + code: 'PERMISSION_ACTIVE', + isActive: true, + }, + ], + }, + phoneNumber: '628123123112', + email: 'test@kadence.com', + _id: '613ee8e5b2fdd012b94484ca', + user: '6141b7d9b8822a162d427323', + rememberMe: false, + loginWith: 'EMAIL', + isActive: true, + verification: { email: true, phoneNumber: true }, + agreement: { + tnc: true, + }, + signUpFrom: 'MOBILE', + signUpDate: '2021-9-13', +}; diff --git a/packages/auth-api/e2e/role/role.admin.e2e-spec.ts b/packages/auth-api/e2e/role/role.admin.e2e-spec.ts new file mode 100644 index 0000000..df346d1 --- /dev/null +++ b/packages/auth-api/e2e/role/role.admin.e2e-spec.ts @@ -0,0 +1,458 @@ +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import request from 'supertest'; +import { faker } from '@faker-js/faker'; +import { + E2E_ROLE_ADMIN_ACTIVE_URL, + E2E_ROLE_ADMIN_CREATE_URL, + E2E_ROLE_ADMIN_DELETE_URL, + E2E_ROLE_ADMIN_GET_BY_ID_URL, + E2E_ROLE_ADMIN_INACTIVE_URL, + E2E_ROLE_ADMIN_LIST_URL, + E2E_ROLE_ADMIN_UPDATE_URL, + E2E_ROLE_PAYLOAD_TEST, +} from './role.constant'; +import { connection, Types } from 'mongoose'; +import { ENUM_ROLE_STATUS_CODE_ERROR } from 'src/role/role.constant'; +import { RouterModule } from '@nestjs/core'; +import { CoreModule } from 'src/core/core.module'; +import { AuthService } from 'src/auth/service/auth.service'; +import { RoleService } from 'src/role/service/role.service'; +import { PermissionService } from 'src/permission/service/permission.service'; +import { RoleBulkService } from 'src/role/service/role.bulk.service'; +import { ENUM_REQUEST_STATUS_CODE_ERROR } from 'src/utils/request/request.constant'; +import { RouterAdminModule } from 'src/router/router.admin.module'; +import { RoleDocument } from 'src/role/schema/role.schema'; +import { PermissionDocument } from 'src/permission/schema/permission.schema'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { RoleCreateDto } from 'src/role/dto/role.create.dto'; +import { useContainer } from 'class-validator'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; + +describe('E2E Role Admin', () => { + let app: INestApplication; + let authService: AuthService; + let roleService: RoleService; + let permissionService: PermissionService; + let roleBulkService: RoleBulkService; + let helperDateService: HelperDateService; + let authApiService: AuthApiService; + + let role: RoleDocument; + let roleUpdate: RoleDocument; + + let accessToken: string; + + let successData: RoleCreateDto; + let updateData: RoleCreateDto; + let existData: RoleCreateDto; + + const apiKey = 'qwertyuiop12345zxcvbnmkjh'; + let xApiKey: string; + let timestamp: number; + + beforeAll(async () => { + const modRef = await Test.createTestingModule({ + imports: [ + CoreModule, + RouterAdminModule, + RouterModule.register([ + { + path: '/admin', + module: RouterAdminModule, + }, + ]), + ], + }).compile(); + + app = modRef.createNestApplication(); + useContainer(app.select(CoreModule), { fallbackOnErrors: true }); + authService = app.get(AuthService); + roleService = app.get(RoleService); + roleBulkService = app.get(RoleBulkService); + permissionService = app.get(PermissionService); + helperDateService = app.get(HelperDateService); + authApiService = app.get(AuthApiService); + + const permissions: PermissionDocument[] = + await permissionService.findAll(); + + successData = { + name: 'testRole1', + permissions: permissions.map((val) => `${val._id}`), + isAdmin: true, + }; + + roleUpdate = await roleService.create({ + name: 'testRole2', + permissions: permissions.map((val) => `${val._id}`), + isAdmin: true, + }); + + updateData = { + name: 'testRole3', + permissions: permissions.map((val) => `${val._id}`), + isAdmin: true, + }; + + existData = { + name: 'testRole', + permissions: permissions.map((val) => `${val._id}`), + isAdmin: true, + }; + + role = await roleService.create(existData as RoleCreateDto); + + accessToken = await authService.createAccessToken({ + ...E2E_ROLE_PAYLOAD_TEST, + loginDate: new Date(), + rememberMe: false, + }); + + timestamp = helperDateService.timestamp(); + const apiEncryption = await authApiService.encryptApiKey( + { + key: apiKey, + timestamp, + hash: 'e11a023bc0ccf713cb50de9baa5140e59d3d4c52ec8952d9ca60326e040eda54', + }, + 'opbUwdiS1FBsrDUoPgZdx', + 'cuwakimacojulawu' + ); + xApiKey = `${apiKey}:${apiEncryption}`; + + await app.init(); + }); + + it(`GET ${E2E_ROLE_ADMIN_LIST_URL} List Success`, async () => { + const response = await request(app.getHttpServer()) + .get(E2E_ROLE_ADMIN_LIST_URL) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`GET ${E2E_ROLE_ADMIN_GET_BY_ID_URL} Get Not Found`, async () => { + const response = await request(app.getHttpServer()) + .get( + E2E_ROLE_ADMIN_GET_BY_ID_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_ROLE_STATUS_CODE_ERROR.ROLE_NOT_FOUND_ERROR + ); + + return; + }); + + it(`GET ${E2E_ROLE_ADMIN_GET_BY_ID_URL} Get Success`, async () => { + const response = await request(app.getHttpServer()) + .get(E2E_ROLE_ADMIN_GET_BY_ID_URL.replace(':_id', role._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`POST ${E2E_ROLE_ADMIN_CREATE_URL} Create Error Request`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_ROLE_ADMIN_CREATE_URL) + .send({ + name: 123123, + }) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.UNPROCESSABLE_ENTITY); + expect(response.body.statusCode).toEqual( + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_VALIDATION_ERROR + ); + + return; + }); + + it(`POST ${E2E_ROLE_ADMIN_CREATE_URL} Create Exist`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_ROLE_ADMIN_CREATE_URL) + .send(existData) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_ROLE_STATUS_CODE_ERROR.ROLE_EXIST_ERROR + ); + + return; + }); + + it(`POST ${E2E_ROLE_ADMIN_CREATE_URL} Create Success`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_ROLE_ADMIN_CREATE_URL) + .send(successData) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.CREATED); + expect(response.body.statusCode).toEqual(HttpStatus.CREATED); + + return; + }); + + it(`PUT ${E2E_ROLE_ADMIN_UPDATE_URL} Update Error Request`, async () => { + const response = await request(app.getHttpServer()) + .put(E2E_ROLE_ADMIN_UPDATE_URL.replace(':_id', roleUpdate._id)) + .send({ + name: [231231], + }) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.UNPROCESSABLE_ENTITY); + expect(response.body.statusCode).toEqual( + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_VALIDATION_ERROR + ); + + return; + }); + + it(`PUT ${E2E_ROLE_ADMIN_UPDATE_URL} Update Not found`, async () => { + const response = await request(app.getHttpServer()) + .put( + E2E_ROLE_ADMIN_UPDATE_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .send(updateData) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_ROLE_STATUS_CODE_ERROR.ROLE_NOT_FOUND_ERROR + ); + + return; + }); + + it(`PUT ${E2E_ROLE_ADMIN_UPDATE_URL} Update Exist`, async () => { + const response = await request(app.getHttpServer()) + .put(E2E_ROLE_ADMIN_UPDATE_URL.replace(':_id', roleUpdate._id)) + .send(existData) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_ROLE_STATUS_CODE_ERROR.ROLE_EXIST_ERROR + ); + + return; + }); + + it(`PUT ${E2E_ROLE_ADMIN_UPDATE_URL} Update Success`, async () => { + const response = await request(app.getHttpServer()) + .put(E2E_ROLE_ADMIN_UPDATE_URL.replace(':_id', roleUpdate._id)) + .send(updateData) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`PATCH ${E2E_ROLE_ADMIN_INACTIVE_URL} Inactive, Not Found`, async () => { + const response = await request(app.getHttpServer()) + .patch( + E2E_ROLE_ADMIN_INACTIVE_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_ROLE_STATUS_CODE_ERROR.ROLE_NOT_FOUND_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_ROLE_ADMIN_INACTIVE_URL} Inactive, success`, async () => { + const response = await request(app.getHttpServer()) + .patch(E2E_ROLE_ADMIN_INACTIVE_URL.replace(':_id', roleUpdate._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`PATCH ${E2E_ROLE_ADMIN_INACTIVE_URL} Inactive, already inactive`, async () => { + const response = await request(app.getHttpServer()) + .patch(E2E_ROLE_ADMIN_INACTIVE_URL.replace(':_id', roleUpdate._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_ROLE_STATUS_CODE_ERROR.ROLE_ACTIVE_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_ROLE_ADMIN_ACTIVE_URL} Active, Not Found`, async () => { + const response = await request(app.getHttpServer()) + .patch( + E2E_ROLE_ADMIN_ACTIVE_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_ROLE_STATUS_CODE_ERROR.ROLE_NOT_FOUND_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_ROLE_ADMIN_ACTIVE_URL} Active, success`, async () => { + const response = await request(app.getHttpServer()) + .patch(E2E_ROLE_ADMIN_ACTIVE_URL.replace(':_id', roleUpdate._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`PATCH ${E2E_ROLE_ADMIN_ACTIVE_URL} Active, already active`, async () => { + const response = await request(app.getHttpServer()) + .patch(E2E_ROLE_ADMIN_ACTIVE_URL.replace(':_id', roleUpdate._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_ROLE_STATUS_CODE_ERROR.ROLE_ACTIVE_ERROR + ); + + return; + }); + + it(`DELETE ${E2E_ROLE_ADMIN_DELETE_URL} Delete Not Found`, async () => { + const response = await request(app.getHttpServer()) + .delete( + E2E_ROLE_ADMIN_DELETE_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_ROLE_STATUS_CODE_ERROR.ROLE_NOT_FOUND_ERROR + ); + + return; + }); + + it(`DELETE ${E2E_ROLE_ADMIN_DELETE_URL} Delete Success`, async () => { + const response = await request(app.getHttpServer()) + .delete(E2E_ROLE_ADMIN_DELETE_URL.replace(':_id', role._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + afterAll(async () => { + try { + await roleBulkService.deleteMany({ + name: 'testRole', + }); + await roleBulkService.deleteMany({ + name: 'testRole1', + }); + await roleBulkService.deleteMany({ + name: 'testRole2', + }); + await roleBulkService.deleteMany({ + name: 'testRole3', + }); + } catch (e) { + console.error(e); + } + + connection.close(); + await app.close(); + }); +}); diff --git a/packages/auth-api/e2e/role/role.constant.ts b/packages/auth-api/e2e/role/role.constant.ts new file mode 100644 index 0000000..dcfd479 --- /dev/null +++ b/packages/auth-api/e2e/role/role.constant.ts @@ -0,0 +1,54 @@ +export const E2E_ROLE_ADMIN_LIST_URL = '/admin/role/list'; +export const E2E_ROLE_ADMIN_GET_BY_ID_URL = '/admin/role/get/:_id'; +export const E2E_ROLE_ADMIN_CREATE_URL = '/admin/role/create'; +export const E2E_ROLE_ADMIN_UPDATE_URL = '/admin/role/update/:_id'; +export const E2E_ROLE_ADMIN_DELETE_URL = '/admin/role/delete/:_id'; +export const E2E_ROLE_ADMIN_INACTIVE_URL = '/admin/role/update/:_id/inactive'; +export const E2E_ROLE_ADMIN_ACTIVE_URL = '/admin/role/update/:_id/active'; + +export const E2E_ROLE_PAYLOAD_TEST = { + role: { + name: 'admin', + isActive: true, + isAdmin: true, + permissions: [ + { + code: 'ROLE_READ', + isActive: true, + }, + { + code: 'ROLE_UPDATE', + isActive: true, + }, + { + code: 'ROLE_CREATE', + isActive: true, + }, + { + code: 'ROLE_DELETE', + isActive: true, + }, + { + code: 'ROLE_ACTIVE', + isActive: true, + }, + { + code: 'ROLE_INACTIVE', + isActive: true, + }, + ], + }, + phoneNumber: '628123123112', + email: 'test@kadence.com', + _id: '613ee8e5b2fdd012b94484ca', + user: '6141b7d9b8822a162d427323', + rememberMe: false, + loginWith: 'EMAIL', + isActive: true, + verification: { email: true, phoneNumber: true }, + agreement: { + tnc: true, + }, + signUpFrom: 'MOBILE', + signUpDate: '2021-9-13', +}; diff --git a/packages/auth-api/e2e/setting/setting.admin.e2e-spec.ts b/packages/auth-api/e2e/setting/setting.admin.e2e-spec.ts new file mode 100644 index 0000000..eed49b8 --- /dev/null +++ b/packages/auth-api/e2e/setting/setting.admin.e2e-spec.ts @@ -0,0 +1,199 @@ +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { RouterModule } from '@nestjs/core'; +import { Test } from '@nestjs/testing'; +import { useContainer } from 'class-validator'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; +import { AuthService } from 'src/auth/service/auth.service'; +import { CoreModule } from 'src/core/core.module'; +import { RouterAdminModule } from 'src/router/router.admin.module'; +import { SettingService } from 'src/setting/service/setting.service'; +import { UserService } from 'src/user/service/user.service'; +import { IUserDocument } from 'src/user/user.interface'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { connection, Types } from 'mongoose'; +import { E2E_SETTING_ADMIN_UPDATE_URL } from './setting.constant'; +import request from 'supertest'; +import { faker } from '@faker-js/faker'; +import { SettingDocument } from 'src/setting/schema/setting.schema'; +import { ENUM_SETTING_STATUS_CODE_ERROR } from 'src/setting/setting.constant'; +import { ENUM_REQUEST_STATUS_CODE_ERROR } from 'src/utils/request/request.constant'; + +describe('E2E Setting Admin', () => { + let app: INestApplication; + let settingService: SettingService; + let userService: UserService; + let authService: AuthService; + let helperDateService: HelperDateService; + let authApiService: AuthApiService; + + const apiKey = 'qwertyuiop12345zxcvbnmkjh'; + let xApiKey: string; + let timestamp: number; + + let setting: SettingDocument; + + let accessToken: string; + + beforeAll(async () => { + const modRef = await Test.createTestingModule({ + imports: [ + CoreModule, + RouterAdminModule, + RouterModule.register([ + { + path: '/admin', + module: RouterAdminModule, + }, + ]), + ], + }).compile(); + + app = modRef.createNestApplication(); + useContainer(app.select(CoreModule), { fallbackOnErrors: true }); + userService = app.get(UserService); + authService = app.get(AuthService); + settingService = app.get(SettingService); + helperDateService = app.get(HelperDateService); + authApiService = app.get(AuthApiService); + + const user = await userService.findOne( + { + email: 'admin@mail.com', + }, + { + populate: { + role: true, + permission: true, + }, + } + ); + + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadAccessToken(map, false); + accessToken = await authService.createAccessToken(payload); + + setting = await settingService.findOneByName('maintenance'); + + timestamp = helperDateService.timestamp(); + const apiEncryption = await authApiService.encryptApiKey( + { + key: apiKey, + timestamp, + hash: 'e11a023bc0ccf713cb50de9baa5140e59d3d4c52ec8952d9ca60326e040eda54', + }, + 'opbUwdiS1FBsrDUoPgZdx', + 'cuwakimacojulawu' + ); + xApiKey = `${apiKey}:${apiEncryption}`; + + await app.init(); + }); + + it(`PUT ${E2E_SETTING_ADMIN_UPDATE_URL} Update Not Found`, async () => { + const response = await request(app.getHttpServer()) + .put( + E2E_SETTING_ADMIN_UPDATE_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('Authorization', `Bearer ${accessToken}`) + .set('x-api-key', xApiKey) + .send({ value: true }); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_SETTING_STATUS_CODE_ERROR.SETTING_NOT_FOUND_ERROR + ); + + return; + }); + + it(`PUT ${E2E_SETTING_ADMIN_UPDATE_URL} Update Error Request`, async () => { + const response = await request(app.getHttpServer()) + .put(E2E_SETTING_ADMIN_UPDATE_URL.replace(':_id', `${setting._id}`)) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('Authorization', `Bearer ${accessToken}`) + .set('x-api-key', xApiKey) + .send({ value: { test: 'aaa' } }); + + expect(response.status).toEqual(HttpStatus.UNPROCESSABLE_ENTITY); + expect(response.body.statusCode).toEqual( + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_VALIDATION_ERROR + ); + + return; + }); + + it(`PUT ${E2E_SETTING_ADMIN_UPDATE_URL} Update String Success`, async () => { + const response = await request(app.getHttpServer()) + .put(E2E_SETTING_ADMIN_UPDATE_URL.replace(':_id', `${setting._id}`)) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('Authorization', `Bearer ${accessToken}`) + .set('x-api-key', xApiKey) + .send({ value: 'test' }); + + await settingService.updateOneById(setting._id, { value: false }); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`PUT ${E2E_SETTING_ADMIN_UPDATE_URL} Update Number Success`, async () => { + const response = await request(app.getHttpServer()) + .put(E2E_SETTING_ADMIN_UPDATE_URL.replace(':_id', `${setting._id}`)) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('Authorization', `Bearer ${accessToken}`) + .set('x-api-key', xApiKey) + .send({ value: 123 }); + + await settingService.updateOneById(setting._id, { value: false }); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`PUT ${E2E_SETTING_ADMIN_UPDATE_URL} Update String Convert If Possible Success`, async () => { + const response = await request(app.getHttpServer()) + .put(E2E_SETTING_ADMIN_UPDATE_URL.replace(':_id', `${setting._id}`)) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('Authorization', `Bearer ${accessToken}`) + .set('x-api-key', xApiKey) + .send({ value: 'false' }); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`PUT ${E2E_SETTING_ADMIN_UPDATE_URL} Update Boolean Success`, async () => { + const response = await request(app.getHttpServer()) + .put(E2E_SETTING_ADMIN_UPDATE_URL.replace(':_id', `${setting._id}`)) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('Authorization', `Bearer ${accessToken}`) + .set('x-api-key', xApiKey) + .send({ value: false }); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + afterAll(async () => { + connection.close(); + await app.close(); + }); +}); diff --git a/packages/auth-api/e2e/setting/setting.common.e2e-spec.ts b/packages/auth-api/e2e/setting/setting.common.e2e-spec.ts new file mode 100644 index 0000000..8c44cf7 --- /dev/null +++ b/packages/auth-api/e2e/setting/setting.common.e2e-spec.ts @@ -0,0 +1,158 @@ +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { RouterModule } from '@nestjs/core'; +import { Test } from '@nestjs/testing'; +import { useContainer } from 'class-validator'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; +import { CoreModule } from 'src/core/core.module'; +import { SettingService } from 'src/setting/service/setting.service'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { connection, Types } from 'mongoose'; +import request from 'supertest'; +import { faker } from '@faker-js/faker'; +import { SettingDocument } from 'src/setting/schema/setting.schema'; +import { ENUM_SETTING_STATUS_CODE_ERROR } from 'src/setting/setting.constant'; +import { + E2E_SETTING_COMMON_GET_BY_NAME_URL, + E2E_SETTING_COMMON_GET_URL, + E2E_SETTING_COMMON_LIST_URL, +} from './setting.constant'; +import { RouterCommonModule } from 'src/router/router.common.module'; + +describe('E2E Setting Common', () => { + let app: INestApplication; + let settingService: SettingService; + let helperDateService: HelperDateService; + let authApiService: AuthApiService; + + const apiKey = 'qwertyuiop12345zxcvbnmkjh'; + let xApiKey: string; + let timestamp: number; + + let setting: SettingDocument; + + beforeAll(async () => { + const modRef = await Test.createTestingModule({ + imports: [ + CoreModule, + RouterCommonModule, + RouterModule.register([ + { + path: '/', + module: RouterCommonModule, + }, + ]), + ], + }).compile(); + + app = modRef.createNestApplication(); + useContainer(app.select(CoreModule), { fallbackOnErrors: true }); + settingService = app.get(SettingService); + helperDateService = app.get(HelperDateService); + authApiService = app.get(AuthApiService); + + setting = await settingService.findOneByName('maintenance'); + + timestamp = helperDateService.timestamp(); + const apiEncryption = await authApiService.encryptApiKey( + { + key: apiKey, + timestamp, + hash: 'e11a023bc0ccf713cb50de9baa5140e59d3d4c52ec8952d9ca60326e040eda54', + }, + 'opbUwdiS1FBsrDUoPgZdx', + 'cuwakimacojulawu' + ); + xApiKey = `${apiKey}:${apiEncryption}`; + + await app.init(); + }); + + it(`GET ${E2E_SETTING_COMMON_LIST_URL} List Success`, async () => { + const response = await request(app.getHttpServer()) + .get(E2E_SETTING_COMMON_LIST_URL) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`GET ${E2E_SETTING_COMMON_GET_URL} Get Not Found`, async () => { + const response = await request(app.getHttpServer()) + .get( + E2E_SETTING_COMMON_GET_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_SETTING_STATUS_CODE_ERROR.SETTING_NOT_FOUND_ERROR + ); + + return; + }); + + it(`GET ${E2E_SETTING_COMMON_GET_URL} Get Success`, async () => { + const response = await request(app.getHttpServer()) + .get(E2E_SETTING_COMMON_GET_URL.replace(':_id', `${setting._id}`)) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`GET ${E2E_SETTING_COMMON_GET_BY_NAME_URL} Not Found`, async () => { + const response = await request(app.getHttpServer()) + .get( + E2E_SETTING_COMMON_GET_BY_NAME_URL.replace( + ':settingName', + faker.name.firstName() + ) + ) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_SETTING_STATUS_CODE_ERROR.SETTING_NOT_FOUND_ERROR + ); + + return; + }); + + it(`GET ${E2E_SETTING_COMMON_GET_BY_NAME_URL} Success`, async () => { + const response = await request(app.getHttpServer()) + .get( + E2E_SETTING_COMMON_GET_BY_NAME_URL.replace( + ':settingName', + setting.name + ) + ) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + afterAll(async () => { + connection.close(); + await app.close(); + }); +}); diff --git a/packages/auth-api/e2e/setting/setting.constant.ts b/packages/auth-api/e2e/setting/setting.constant.ts new file mode 100644 index 0000000..f64b415 --- /dev/null +++ b/packages/auth-api/e2e/setting/setting.constant.ts @@ -0,0 +1,6 @@ +export const E2E_SETTING_COMMON_LIST_URL = '/setting/list'; +export const E2E_SETTING_COMMON_GET_URL = '/setting/get/:_id'; +export const E2E_SETTING_COMMON_GET_BY_NAME_URL = + '/setting/get/name/:settingName'; + +export const E2E_SETTING_ADMIN_UPDATE_URL = '/admin/setting/update/:_id'; diff --git a/packages/auth-api/e2e/user/files/medium.jpg b/packages/auth-api/e2e/user/files/medium.jpg new file mode 100644 index 0000000000000000000000000000000000000000..60b5d528f05e379420d27012858c68236ba91ce2 GIT binary patch literal 308701 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
    C78=M%c^|gHjoE>&o=1c17J$gs z9f1`c(*Wl1BS9`9r@!L1z!C~!`u+I%bA?wy)-yrOjS?rX47r@>lL^2_75wi__KnyvK#vkn@kDRP|jqIxwFv0UDB9WG{FbAH@Z((_cAQH%z{lJus@|~u~03% zp>%Zq0T2+dKcF$ao<5;b8?q>K*#bSy9t9Y7&z1Vau0xO5w(7aZN2X-JG;bW9t+X8{ za7X+A(#1G(iwD?e5BMoXlrebiQ1WokxW}L6Zv>BdK(Yr^Ew2TJ5m`grM3=3+$Hzoc zcGAILC-CdxkB;}0WIq>+${&*!_N`GvYESdf7%0;4Ze2EMw9t3)27l^(7(Elcyxc#6 zG&TQhn2!GdU&}ud=dE|k+@NYEU+e$+5I?t{gG@&0+EW}zp4_jURpPH*ZLB+xW^ewR zmN)D{9VYW*YnnHvt`!uLfi@f>Ky7!8o~R27qAm_gA0iS11(E5-}ZqSIU(&{)=QLTBq#4R`+)n*Dd zejem0=F}XQn#kv{Q4CTv zzSk9~CYrQFVtS3x^`vx#nf`@5gPH{%2(;2mHz$T;XBpfUl6oM5o?FNlBqIa7)4 z2T#k7u2I5U1m{7O?msKJe@(X>)#$cPeF*-93RA`1b9-z_nej3UNmC?25$p-G8l5US zyxEtA-INO>W2s1#(fKk{42k(9iqR&k(2b3j(a?}*N4iTWnUEBhy-5ZOe*WU7{;1qX zgp9P9sD+ccNe|^$_+yG2x^fd7dU1EYK%&f~K*90bOAQ?U!m)g8WnnSA8!7z{K)w4= zWjyusNp0LyNl`7f;RE1Ocf3jR58!(K@{A-bX`12omwMVy?9U$K8w+Yk^O6ulXz_Q% zmb80Of%M5mlTusii?zE3$O_Q;|D_kW(aIRoZfvK#2kU+=W-- zF5=S`u5;Txnvi%6qa^iUwCm&;`n2c0(sT0mnfw?<#PnSSN z7l#RqrIZ{Y~B7O$O+I+AS2O5PdNS!P<*V1Mlr1e_f-(h@|2v z`_EJ-B|xqQvw?+s#TCa_uc(08Bk$lMc zL%up~M5P|K!`DC_TA4Q8|9es3d32^{&lV@wujf{5iKQp(SUFaZMJ-F?6KwY0U)R~G zcnC#K|B7;j@5h<#nZkR}b^vo&_u7z1k(@J!x!Ly`p)XD;sC{Q*J(>y3`Q^!MI>RV% z?Yx04vBKRxQSPC=bNQ|8Z%5c(^`!RFG@<;Z2P%Q7ht*7YZb_e+t##;Ju&gDn&jHc8_K@fgnrs8EyXy;9leclb8 z(sy`=bwzRb{%J&wWHYHcDWPpY8OD<}p3;sh`ul~`YAEeP!NIpl;7y5H_mKN7HfCJ+qF61Gfj{yrN6t`cUSTEz$n4!6n-NJ zLMs^kpDvfB!X`U)R0odzcgu$`A|!cvvD6Z1pNRV-1w(jT%u4Pepj2m&&{Z_q_NKVy z@w}ikE#IS?JKQ$aq~AAGr;M~LYXhx~1^Z9&Y|lcswN#IF&iAcN5NThX+a61KEpkBD ztr@i5X?_Kju~7F~RA)9dn+{Z%i(_MBXRhDoKD7S5U21QTmMQ0k3uv|_iL zo0~)bzf3^Xegl@>z*ZlXAXUYJmafJ3Y%@)DV5W?Nkvw>ft1T)lU zjfHUPpnAFzG|l3hYduK2I6KD@f24oUS>G%7kjlR&q6W|;FJm+~Y>tJjZ^oOAoA<5bFXOI^nANZ6j@UVq)HNvDRl=&T@+|_FXqs19Wmh5~wEtyt!n2`qz z9VJWPA)yQ$_UZl)v52|GQQ_t!3Pg-KjTAD*v4=Mv%-7tJbQCFi{D77xazmN)I6)CZyL#((ACcnWi~v&$aR zPGL&oUV>hqTaLEomnEuZZl2=yA4P?B=MMPgts8`xKb0)pj}FQ48&;Ist&XyRUi)|< zb^zz9qz~SeS#-idbl3h1L~nH1>km?%Q0gr`5ThkHqj_z#l0FdQ+ypH>EN6pv=Qb+z zMgv7inK#w*1hB^vm^&K|1_<0DymVvQyMx??r?oX{s?=7H* zqRP(h@bXh8YCPitTm89#O)IjHU%@EX_1#wvS90m%d#3~eY*PfUuC|V@8|b3_G(Y6D z;Un8us+b9ToaM9DM{WE&&3wX3%gp+9K@;aS!(^@vcv+6k?lR1p-P5%#5y)nQ6;`M7 zR=S5%Wi@+s@> zU_2s*yH|rWX*YNm>n=veAxEBgpU_m24&jT@YnYs!h%MaB{mpyKJi>zu$~!X35h(sl zC86)0is%lgt8R(`MN@agGO4cRE0ZfJk@5%CaOCMRrbpFO`H1rr-W5 z5=ATOxoH$TMeh)gFcPAD6eW58!;eL4-iV<4FLJSUMN{))!@e_i_E!b0cLC+POKR(S zIpP&8HZ5`c_P-}@PRrWPC?avuWUcA#r(A%}-hdb$BaF{@UaELv_Pm#wirt|cm zWmadjl+V(d8<|#pceky*6!-GRl1(x!G41RhtIy@D z@+TxAUz5Xw$p1D76PR^Eg-eQtD^uoAeDnE8JzC~l{vWpo#z(qMeH9{>Ag>N#%pxiw zm{{>Zv^J5IQS53`VYncri9gxC$nGlfz;%@F6r{f*BWHqXW5>!ai0i!#5P}aAR?fa^uY{6 z9(M^*EIfsg|5GJ&qeUxXwEK0n<-xP^$@LZrN{YxMl9Da+_QuZ{k1@H1`AO+azTYzA zzmI|XTDH=!eH?ClO3PtYP-~`MtGdC678~|1n&X~KO{$=Y)r@3(-b8m=Vfy1oj+bf} zZgR||KqtE1iVx~H;VPP2*gqc^e%lbREou6-D~huk)!i!K?>3moWvQKFZ|#K@5}{oK zUIb>Ub%QzQb&C(X_(}Oo58I(}l*zt+>b7S{*SrfZahAnowihKukZ-TbL`!A{iHQV9 zc^#3_7@Y8a#@UGqtXJsC{awN#UhNoo`rU0Fv*E-Ly)0Rd5oP5>psj#tuP`Ml)K?+< zdu4rb|4ZzX*v(X#%cQ4iQC9lW6r@*Rxo14E{qWMe{kzJPOD}u&WP?;Z(UOh>J@Fp% zK~GX}4ZN)74fle?vb3cU0}|U1ne__zLhzcLI#5$Q>!ZmG?g8n%aiDc@>4obN#2=(Y z%Z&;HH8s%kB0WALI_o7Q!Zo#*rWTi1AHBp~o+Pk=maW9Ox^ny#O>rr4rI4y|Cxpv@ zqCpCk@`pDt4B;(L{7fkCA`i_y`IzZ11=-bf5+qj;nak_hV$1T=>F1iq7Z_8(DkhMF zdpf7x<#Lqi)noNgjT=L1ek!NOT-?XbK7RM^mU{%o2aGL!Z$U)zR5-$%P8&9*-t?U5 zRhi-eX0uxh#YP_Lh+WWHk;kC(f zC5kktMq{2}q$cQ<@<1&?2{GT}$Ga(b?2q_wq5Bu)u{6nOpbt-@?l4nFmeB5hGEonE zj{gUb-qdbQiesIQ9J;5_Ok%oy24fzd~Z z_zLDAVBN!sYwX9Lj9x}(Or1kC_iP(y9|aJjwLm}ArykO|MpJj(^d0Su@Jv9oNqzH7 zCJRn}NKA`EPD!SmQXJv_r18KP@f=EdkE;YYt|+R98M8$21e%MgJwXm8Q362#X*55w z2X6G6KYQyqXtM35yC|cz7oWWuC)$Q!0!TMS_@Tpi|cJ!yIMmO3bGEQg*d`W)r4*+qK zs)V4WEtI&ac(NggTlPvhr7uCQMf!lCB%x%UH&je-%!6o)^+;r!bMjP!BufnsPJAH)OYh45?X?=Dyg*ZY=8pKJc@Q-GH$p(&p6}M~* z?9Ul||NYRg=C#g=`MJ?Dj5E{e16$Crur`Rg7Hy?MUvOEeO`l;iU7wXXyEbJ(tcsNP z0EV17!I>oIYSej5V9>ohtzOuifb%ru@bdlY{Gh*dpWun#T#1Q@&zi(+;OEeHTqG?`8R0~9LNgrOZz zd){q7)%`nV&opQjftk+O9_vs zT)nVhRMz&jYcZc$FW!;lRej1(G0Ja~&SSK~Td;__E!EJ+%AxG1H6I<$BO z0OX;|e>??Cu+$b8_V_3clm81oC(sr~{d*U{lW_H$pCpw(N zJ+s~In6|cZZ%VL(>Nq-<{bIJB=9cMo@#S)std^Gz2tRcIWzTqYSI+46U0f~)HDI`` zwS~@0|8$luz-!~rbJ#lAOWZ_~@v56*lK>yhcp8)L-~0n?wliFSYm1!8$(hc;W%o-a zmrQtZ&hc6+PYbi}_50>BmSv^R1XdvEL+uOj4^FPPv?1HnnuE_*olxCa^WJ0)M*ff@ znJhoRg3&z0M@G!L@*a97+ItHz|HJ%I!Rj@OCGI%@0CsXjBtknHLQNXTS2X29*X8SK#>WKD%< z6Qkrzn0?e1)4c_lW3OASl8%vk+D({O9pUwiZM4L;i@9GS_F058sh~owspCHWE`+DA z$Ji{HIHpryQ$Ar@b3~@op<7kM#F3)AH>Akse|pb zkC+=$91L=Fd)=gdH6k@m_>+E=z*VS5nW73WqxqDi>R>fy@5O^Hh^Ks4%O*H0HL2S) za|andSn7uqH$dqr20R$Oyh>Sh`PJvSM!ZMB5OKsS1hjLBg-^c>xLRB8WK1}?FP8cd z=A4(rm|dX4Vxz&vc~S1pR;w^w%;YlnpuTeD_7Bi^L7Us33u(Gz0f9XKDJ@8h9P)_( zF=<2r714TFm#5YwZD3TPm+eiCGb*jns+KQt7cS7X|gB(cfMAMzm4Bu%3|$NROA z{53L5wi5?WeTQ}>87uJDG2~F)4iM>_TL=FpadjXqCx6v-O!Ss3mj8x*bnxb{kDZNc zyO8B65sTPUKXc)3`%*QRZrqPoU(>9gzgdtKe$EVi-8@#=mLn*)Uv(?&Ik2)GO<0S( z6J1Di@cjpH9~soLvmfMEA#fEO$aa-l62vLVPl;}y>xzouFO2QUsbpv-HCxuzA+#nX zYj(KAuCvgKAyw*GgAshSVq4g{{F6hVJ9^$U)t_ovno#?V44ylckheG{*7?hbcBbAG z69F+)8cYK59D^QM9?$5B!T}{zdhP8j)oy|NTk5U~1Whe5WM!|N9Kf3W_yUj1vp!Gc zmXn2YK@@Arj68umT~Nla2&1?&-$3p8>ql$@SG*})ZX#SuZZ0W3P0)yj+~UF|tMVXH zNBTwkO8vGeXk7~ru^=msZWtkRXHQ1~rEZC6L!VR7=IF7UIw46ziE(ZGX?%UYM6rLj zr?nHbY5JpM&XBQ|aD?AF6BkxRqW4K+V@z=q3})$G2ucQJ;?v%EH$L5E6FGjWQ*G;+ zD)rJghfjTpWKF4mIqY;aZJ_YZsRwX{Hc?A)BLuLCu)Y2KN-=SOqDS{frI*c^z(AgN zbHkqFuYB?c0et#s$HLIT8jAivnreM*VJk_(f7X=O0GI@P`VVRy$o@}X#@X@1sNtp$ z>Q=&iDo6EDMHPBnIxP(wT^qTQaE+LawEn$bINH4(`z|SX=A~!5tfn%HXaLj(t+wx0 zL+NET@0f6wAz5D8jdWVtHnJYu}=rLMUWRo_@U)dcGrEo_34~AP< z5?2BT+-*Vz%%D8+hWM;W2ZBkP)EIjY=47R9@krG|TJkWs2zzK}14_7Z%2n#hW}8aq za0;aVWa5qEjF16Yh{h$xwL-9&6irGJA`?llDNRJ4LYpV$`xIbMOanfhQ^D$%lv+|} zl7`SKix87W*=RG_^B4;j3(hE|oM*8e&m;&`Ww9#wL^-F$m`_wlV2RRz=w>Aj&JAEI zzqkrgs$(c2Tw^m+YmqU`W{Px9M)e4~4{gJF&pU2qB&ZQ@-QtmhS{gv2)_Y^DEj6v&_)=@MX}_U z*LIVkkG*{*HO4PQepPvoz&+$JmoL99D=Cz521Wi&YY4fs{sk}L;Wqut0?Km2Z@ChX zfiP%qg(RA*9R77l3d$d6?O|IlDbzHPWl4%WA=-bBR*9o;KbM3})-g$*zpca)9em|& zIcgIxfH4v=vd)8EAA=!@yJd2>i8|Rr!a3*DxOYN+Z)&`$IYYiMDa(dT_sZ~7mbo8- z-5GwZ776M6Prm+#*d&Q(7gpwRGZ&633&8VB`%(h$Z6E6jaS1IysbcF{3sd)G0`c}D zAIqC5s>ox%3U)MrU+f-`1h?R#frurz$#1PoI2POI_LXa!U*%r8m9#%B;mm^%!xnpK zV8s&Dz0`f-!4rR^0n3|tu<^|SHAT5rZ<#U7WGzyYRhwKKe=~6B*M8SlqX(PzvBFY} z9<)08Qy@SkV6@ZrEj~=a_Ri)g&3BsPTTsa1QfFj!7E8F}CV|Kdz>Ivc7%{MvK>KKv zeKd!{2)4klD7&8-TkCM<8p#ua8xo@014FuKFRz)+JAkwoDT?TpZ~bsNHAbjfVFaIs zeE40APz(rn%dSbIKJ63lcJFU!E!d>pm^E1eN?WbM*Yhdcdn(VBw3vQvrahdS{{z5n zmoIlVV?4VReqP|qaTiqo>b59qvjm8Xo_(2f=FC#FdQ7~2o5k61O%*(^>it@y738}p zYpYVVXBX3>U;ET8=#csq?dcNke>1j)AJwa~QK0kzPT^hgG zVLsN8r)bg<*?+LUnK0H$pch|WOe+IL+Eg136F)+_%v5X0p?hI+VkhZeqy$~wphGX= z1(X$gA@&Sx(8q62sv_(gtcBDnm}_j+?iy*RAMw@Z^T^A^HFWz@5(4)=&T{XERQ(}D zBZCVA5sF=zQj4q8xdpqvV!vG=t#N5QI#R(*X&y$4`rgj)#E!X{PGxnZIAILV=wnW3 zpWwQ_m8p28Z`s3Cr5A*4P|K+ zDr@8Rw(j+nbVh1e1{`a|UTy`%@_~;>3~vk^lQ|5IFrwzyjtxIY8$>0$)8HZ7#3Ys$ zs;+ws$%%d|0bOB{ghF!KDvI={PoTI|O_htY|HqDNDxwuN3g4~KtQ<7_QsWM2dp>sl zo|ndrt^>YeRGbk+Ee@w<@~50O@_8Jg=~N6XRhPs(<9oPq<)q8^`+pZ*h^t&LOaH(} z0a4^Mp5QBub$qDgHW}mPqo5^=kztcU9E}eu-Pm=7;hM|Jj;+(0CN!(6oFxNTyp8Mj zXC_MCFbyGHU%`@RAO5QPTVp8n1y0TaTRpZYFQ?(zr7{jiB%4pX{xmFb7m_(hmNn=8 zXrJ@3n_xvo`+IxknR@LMxBO{~=y`PIU4mG_NJC=SoS34a3I0GB7P9Y$oQQ5@Fwkht zTsQ?^SjVhbIBKsJ&MeR!BMJp6aBZhkItTLYJSnKDQ%1iZa1(}7z-YODzq&A34Oe3q zTn%y8o-2+)l)~QFmx>hVh@^5UGc}pDfrgB0WuI!mvW@@c(%(I$m}6GTu%SMBdZyY# zR1B1#YbRom$|<3ZaEmW=aEr`mb_**6imM*3QKJc=kaEka16&pdzd3FBKid^rU9FXr zb8COv;>?%Yt{uMy!3(}TJ-Y_f7|Tte3HkzSltXU#QtE9&mk z(lA%@K*pHXKN5RJ*tOKW^=w@}V-j{~*q8=@)E$I$+VB)?>m0gU^&G`I5=PavzzC*#AT3$V zm=b-~QW~tqvW|gqob{1k#24G|{|T~1t%j6l!UVJ`-wkynRHU9+bm@^H>bWR+muu^T ze3I-xMHi4uL&!{H;-sjh0{04gnZb8vrH%rv5uex2GFZl?i*2Ux62pkb!-!@fWC}@s z@QF_*X8IV!9fk;HoC$!8)Hg!blP-dW`oj>Jd>`GS(M!ek6kFfC;Cp2@@sk7zM z9o^s4z|)vn5tDSBh0sYQsvv+5qa8L@^h;-%m^d#He~)Mx4?5c3Q0teuAGGm-u(qJj zMO9)kC1dgBmVIcdghR4e>LzSCZINi0+AMRku(SRWq?nG0a9R&Jr;ZdBX{XNlyos7G zE!8f_5Xue?&QS1dl(H4Fo+k7fR)9gk!HHDvHH=KTw-%EP0t>S)M9Y6kR*pfKjm92B zkccL&7157xN7fH5kYC))>}=F>v$~oxn2YL=>dZF7N0@6J%&`x{Z^#H>@oa8tN&pV# zvdqm_PRtjlG=>R2202rkW*ZJi=~1T|;+_~SMO zKJ#a*gSJ#I%`ui`pY3YrYsFQy$G>*Uh+W!I>eI<$vtmynjJLLb?Y8$~7j!m6^-RFq zQp+6qI?~@ys|j;k1PDFUgoDVk}CuMF?9b zQm{fa^YPKd=+G=K2grrlIClW%u%h%@?4|`rp?is#z2rPE@cx#|SQ@<#;+f0%3mb!F zBatoF_o~tf2HGGx(($sQ&&jpp$hp5X{9Vnv=lHPPMCc`dY8L7AAg66LM+_xBI;!)QXrk(-#H^0bf^-wN{gywTr$+M>tEoGj!lE$M$`Nm!0qR5#B{$^px* zD6j~nn}9l6jH==^^{M{3{9^%C@r$EYSCa;-5ev}_9jy3V6k?Nx);6iuzET|vd=VYL zA~!g}*jnArLw>Y|JdPKRT#Re%iYrV@Z*J_`9b^<{O=1$M+I2R{MJ8pGr!QYIx^p>d z8^eqoe(Rp-rAF*gmx5EeIQF(8mQ=<{zAJ*~Xihb;T(a0)+F^Fv18`(}w_Wt{J5x5u zh+h3=D)7Cp%D-%7))yN-s;O}jS0Gr`D1KgCi>YiiIz#lRcBiYV(TY)``s6-b&9G0e znpF)#eCb(^i>faEqxXGe*vk>TpQ{~GfJgV%P zdNyzkDInrlTBWzN+FB{Fd1iRzq@TZH=8K5o*uvxt8W8=-^fi)no~d+3&A~TwQAUyk z&CefBI@t@>tRS;G?}My}1Z8`-B#mpp>ofkV2wsC4H|_g%?HXlD&L_ zZ-UJE{vwNp+^HP5O7}hD5}b;NIJSz~4 z6ioW{0bcWOJN9I*X7~(s)SW&KI#B=?KTFsNaX#7k3JzbuYHXW?TYTA@M?^JaOLMhr zGx*U+VCb5icg$ZL+dw>rEQuVZO{bJGE6k00#J0RTBH@-q2dCj)*^C1Yx2ika7Lqh} z3u(52a3AZP7N>V@T2wS|RUdu%M#A;^=CL!&A~zKFIA=orhTU}=iyx$tz;37brJ!}d z@GpN9vjqQWUpeSQPTb6P$g~^e%v+nie}D$2asLPg+A|N}rJzS$4tIIeRdEZxW|8cI zrCxv(;#G`PO!*u)bwmaSqtrG&*AazX41o=3U0b|Hp8nh_T4eP`y$evxw7EWCkpgQ&15VZd*^QB9-WK` zjQIY=33|Dd{KJI9rQ=H0Dzfw<&eGyy$_AN>ZEkZWg=YM1g>~YD0$`)ZXLA}TH;m^Q ze^KMfm-z%s$c<#j<33>Zp<$pkki#jIy~M|ST*~~8-xi@mRHb0KMWc7cxU^1E-JM+k z_gapJ-U6b)Q?NiI(&wZ86cW5`botXL|4A6U2Nfrqy~z{I(0rUlx1rRJ8nTQC`2X-?H9pk4EakJu%~%yDMaoU*Zz?1hHej=x+cG4HuW> zR%VtZgVLqU5ee5xwXuhY=WTvG!{9=0} z01hRz?|Vw0UCU6fbTK3F^Q1Rk03KCPO5C%UC6klK~oc%(r5jKfF67P*zLvayV5_ zPNasM8m7Bty90E?sZc-77^$BE7TjP3l|N9l(0IM{N|rVRUVZfgA0Xtq4`7Sv(ve7* zq^tVx#M?FD!L43?QQ@6#!^_l2Axg)-$FtpXxotJWSA2^NNs+ZzN^&>pQyuA#r878Q zO9M)JE8C@UK5f^SlRl!YgI67O#n(&)$z0~#iKl(IOu+`o=xaldIVClXT33|pR=Jus z2wBdR!o%_+qqdp7&AXoorht^{>$j7#ik|cit5MeJpA{D*`P6S)j8EQ+zoLakTfbNx zErK;}!qx)+0m^bpn(cBHaT?G+Xc`e>s}QeWTJrWz-v)uI=ul|kcn*j+eEKsf#x$M( z-TeOuC zIxg~2d(4%t2d3QuBk}8Q8d>kmX|+pAeIp9`+MXY7c58Ch^Rt#O3ys*$It+||>*%nsdq zl%vis=)O6YGh}#)CNJ#r=I8nH@i!#}%L~R=5bZ(or^15-AnQz%@5y!al5U{H+}uY1X#bTKax~XSHY+LyYU_2$@po%iR|OMJ!TCZIqIzm0<`v6@azaEO+)SPg}T ze}^@P1h5|!L`-BEBWS%JhIl2GrKV$*<<_(HJuDnQOosDqV2%FWNNFI?LWt7Z^To0E ziWnrlaRI41dg@kqkb+u__gyYJ^OpVcc0uK6z4|VB3oS(9(5iFu_pp#Btl}YOCU^_% zWvV5$yP|U8AYQjGJV$@|(vtLUK%))G{k|pd)qZ^1*~?Q(+KjV=r2DW#f}x=}NYM~b z3d&*OO;js)!SE&z%3)I#`KUEuo4clck>A*`8IhN1n@sy(vm(fYlu*}Q5vI<=6bGyj zSkGhwHjHo}JrImplZF5OkXuUK(!GS2eEs^fR#;EZU4t9EM|1bxm;3OX zy=7htH#SMqwWRwW09isD#3@%n-OQ$KNSn0)09>4=HkkqZX}QZJmL8o|VvG}FYl$&r zG{j3LiI%k|-8)FvYM8GGtdy@GMzmD9WZS+-CQmUv?A+`1#>v5}m1 zA&V`G%c9C0HvFI}BT}NQ=Kwl5K=j@{mTgVD>gZWyXo@71VJ)BV6A^X16A)^0K^O;sx$aK91>%`M1<1F~P9P5mkOg?DbnFi7y)HlQgq$iIGn9 zS3caV>D5MN5Mmi^HKYmy0Tvnx;IDx2zms03SLv|!G39PKIxOj;QToZ1*Fn#psxTby z_vmDQ+2Qeby&UygKoEeKw#T0%cU(~$&4IpzA(PLuTY^iT8PH7fS=mo={rpEwxy3JD z!MM+78Y~*bA$r41HwPN$)IpY&3E&(opj&s|>Pg2s=NDjm1yc$eTSO@v&TQ5!+yfL^ zoiy@klQBRMtO)*{Kc8mCRG`69`Qr2q@*hhQ*`6VUm>-t%Hya>K-Vo4xZqltQw)cGdQK*kG=nX3E%U*{J_e{lico>9(T5GW(g zf4DeNHk$n~rbr>x5^gN?-<0<=|NFbVU_}`CBrNiUC{3=rWl?t-PRZdPZz8fz^6`S z)U5~KenhSfd*w0tzO;>;h4RhaQg_Ih?0FdiI&+p0py(^N-LGhm>R$ZawL8r51lh6& z`t3h1zVwfl#Aj(Ib9h(FUGKR;FWq8Ljclc5ShdXVk{?ZkKl0D&>Ka%ao4h#UBdB_z zp}qPb&7{hUnK5*858fG``NpURO#MD3?AA8$ei0pDoMG5dqGrIHxO^fVuxkyU2Yg{D zaDTw`{XYQuKn1@@D2|O71rm2R=|J}5u&VaunscS{+;dqe!YB@l2m}!H>>Yq2I|3?C zOO81@G=VKK9pV1~SLh$01Of=5gaAYJYVBub(>DGgaYFB?>uYOE)m2_h^K?7Rmg;8( z%PlW(Yg+A(pf8!$+iRR`)D-cy+ilE{!q!EoE}{{u>&v9LR2NA+gLMRXa_5no{QAJ(xvatAUe{CMalu^%S77z#o0tf^GYG^?Y*&l@| zRVvm#dW>P$f8{jqNvs(6=uczZE&~;{XXdZHV6guHAE+Dq)wBQ{@R{pXF8Y!PRPj0H zYl|78BrPoSm-}0@Anz?S${vGD5(l^f27Kz(5esPnU4FbLyf9e4qk;M(q6jR zt=(-pk;RrOaNJQGyq=lBVdxGJawP{0{{Ve4{{US0M`=AvGE70w6sXd*8a6KI+;=7+ zM#TCC?Du;@j@ki^*v!Rlj0kC%(;HB4VK{K~XhZZlAFO&68OZG@s^f_C`9A*uw*FkW zN7cTc+5@!q4$S_7rS!Pxm7_Y(Px%smglD(=8SDstjwA7A)23k6dcN&>tD2*%(~Qe0 zaxyhS$lj>qr)@1zV2rWELrPw#EzJ>I=xSdkn$pz}z}TXuRrd$S7O1&4IkgpaOPwo= zpHN9OUb<2T2WU80HK$?!0153Xf2Y${L5^&QqL29V*c?|DeV(*#yJo-Pj1QODfW)i`ob49gya!Npt&EFwH({1{=h5xD5obYzuT^H?ooZ78Vc)1Omds z!T|t>U>%NfdFv;oNLHWZX#T6X9`w`XbL>CPe9!xTWtX@EnfE&l+Mcl(U`tjG5RQ;K;Ar>Ruw*u@9& z%>2Dt54xp!CqjM|=oYvq>&_vBG60>oXAs|Lp<9JPw zV|dQsR!08-RT*e4d__62?P*~qHj%Uzwk85w5O9gEbacZkfv7Mhw+W*O#~?SMW=mR7 zYTM8SfUt<_dN_IzfItKQL?RJa405#d+U-ScXU#|vVL_`11%-u$g@hZ|Y0f(BD20Ny zg&;V%k1s_KCZ^9mKvoe82N+vQ+P1BTG^{Kf0Ej`W3muXFM!Q2hWin!jJhfgO&QhWWQSYzwD)Z zvKBgMX5kOC_4`l1O=_J#scm?`PRhA{HLcjU(fx!wW;0HO=pxwH(Tq#ALiyG<&~Je;0l0deW3| zPCeT~1=5f*T9~YN-{{S4Ia1-7B` zfPUKF%GC`9)mGPuG$8p4&+yjY;&m~_mIi{_SNYD|fp!`)7_#JDK0%l~P6*~X2~Hg& z$rAP6;)I7Imw@Ik0uJZ|r8TZ3ZVShTy8~=*QMI54$`^Qg-624~51McrRU&0Xgf?kV zo@8od36&&{@b%tg$*FqpGT=<&yTgDLCkC+!rxD)X7kYB<1g>J9o;igMo}i+xrx+EVG8w&6(p=?4mc)Htd6dX|FWo6M; zTxXfpwIrG5a&UNd_Le!Ja^IRPMyYARRNIV{79Q~}lZWoBxZ5abt+dSqvwxR0VA8=rp)tv3{{Zs(F08lUX}fpi z-N&5IuEyWoiX@_(ih7k!I=N7f+nPRWR67>Bvhz`)v+n!X@V4RmMyKgvq>-dReD_p9 z6FE*BY496P2P&OO4KswW(iVuQf@^33jnr;CsX`sq#4?+1BPcp78kic4)MW=z&UKLO@GeQ5ajA6XuCV=Hr~!mOK<# z<;sy5WF%8Op$wo5wPz@`;SOpqAcqiY>curQTt>4@W7f9PhO@ULtgYaDXF!{*?d5!D zZbw*KG4X}5d|_%$))tJNUuZ*DT2R(@Y@K6e2x|)(I?lwtG_U~eb*9=ZH_K(!lGW4F z)3;GtlW*3<+vTqU^J^$wEyW9k2|})!#7_YNNXiZ!fGwmht1d%&)|^DsI!23M@$QRI z*H&wLGtEY_qAYf%qjC532~NISoQ2xd4~Yr$SnFz7@J9nAd?8w5Kzq?eI!*sVQ7c?qNnWSd4C43fNY{wiU3gg%+$V zcHtF;j>DCxwB3tP+PJsuv3sy4$t^tmA@-uX*%gCPShWqV;KCucoKeyJl8raDpfqTXb3ydWD&YU50r_a5i1K$u6@3;bX zr>APC)UBv{Zqwu66+s)tP7U@znW$a|#E%2wX&1!uSDem1Pw;wMgF9Q->P%( z`p}M~stT`LGMZLv7dd=eE)J}zM^{vY?PXpU#&>rIYWvBSHsX?^5iKpg*n87-#q%xF z_qM!go8d=F-%DMj@4WRRV9!tEOcMOIlf;2OH5YwH)hD!?}yqC5Rr2IE!{xK?A|RX{fX09w}# zNm>xqRq2|#;uLzi?~P}~ja6vU*UP2O>v4WDEQ52-Q;{bd$wkgtE12M*yIf#2I_m2I z)7)9pt&>XY-928`S5>i%ucMetBM7|LIh41ZLp#mVu#qd=RF4HYu5h9|WVO=s9c=@d z!rH1hHCWZ!e4mPJ1r14IDY}BhPrt0Cd*;-I&e&6KG;Tt+oeG8&C2j#?$|SB~PLu_` zk6A2pYTkugitjQMYNcK3L~7?<@>SntLb65Qq(ZEtR#B^6?WosAyX0%VM7!Jw)xf*d z1sa$aJ{8W4E-iaIXuWLBbHn{tqi%W^iCVKpT)1n;{VT2L>EMzmXp4DR3Bh3$K~7g^ zHKmeLPw|m8P zH8lh^H4)NY^+=(p;f++abk}K_92r*J|C6e(PVBiD0zLYd%*gHVn#n@kjxkK3*3Tx;oRK#i2G!GK}3 z;Y$DnL((|MG&FZu*_S$oN(zSY46pSVFZCg-{YW4lCE*Sx$AiR}iabb-f2lCwaVDIh z#CkxF66kU`kwbyRdwLv4wxPsZUJns1N&G^!mhf4v?^==*K9`2^Lt>2a62+6mT{)dQ zsjPRZo4w}AX?fP#F??^L5!RX&4PT&2_^XGmCsJ_eqo}xXwc3-0xn=K$k{_d zaNbz>r^1U}9Mecz;^wIjQtpKj*SFWVC0Mn_u(3Yf?I*27R*z^s7c(Eky*F^Cy3|w3 z*N|N_V6@wu_nLM@*eG|+ZKE-^+TE|yCOT-%dl0S3HO~lEJqpQ4@3Ny?C?>L30ar0y z=A|0CoQnG3Mb_lf`|at8+N)7DRBO#i3v_OvzSZCEa~SBL*Y}q(n)dLUuP(S6_U4fjvF0~=6?F-_& zIbRf6h#Hd0z9h0`x{l6PqNdTBlGnvgXNtQMgXgE4g+9}FOH$MK`!(i?vJAP)dy}5! z%qjA?C(2xIKSrA*{{VmmgaQEovDg+-{RI%56=&0E{zjbQOS%2h%11j+=i(Ib_RPzb zAHS>a@b{WwbGpw?&Er~I0iT4p7TrgO32C;AyFjIgbcF6@yFcm0G5HE7A9UKtH5ZfS z#P1cK2D}r3QS@>t>Sa;&Jy4I^!1=9DfonZx(8uI2Wc!-gMH!1!H#&ZMV{2fyP}a7k zwD@m#2-AWir%Z$c$?7SAE{GM$YUN~dHz5EB#gZZF*jf{z8-+SBfH@%;a5-5ni3q?( zEU78CDB`Y#{Ov-7nvkc>slYY5oEFz=Qkz+*@g;V*Y{vDg7-_X$uafB{`&n(b-nA>~ z#nSo!x#~17+K&S9sKEEmrd2-ixJt@U8X&-YDbWBWh$qE>BLY@_EC6$B?d*JJ$4P@? zq|dnM@+@??_Z1TN1s)Ecp~0|GXW`l+ejK1jPSNCP+5|M^2?I?~FNP>^G}QrL3{fGb zs22wq1#M(o?U$Pr6Uyy1MT*-MBvEkk63g|%(FJUh>3SlLnWA3{(PQCSG&H3SPL!f4 z`c8z0Xxa?Lqi6$0kfL7*(J1tt9yM13v{s>RtM*yeR7U%!wFQ=?BmOHzlJR$} z(4NZr?x|J#H|cpP>;0K8(JO}qxTQebm9qVBo`#{M+qrIKmeE^ZQ4WG*A%2U9LUeaQ zqGNiOK(HA!BNf|9M^8znpoHnlofi?3T^Ivhqh1ffQ-JL$QoaWg+8s-vak<3PRCsah8Qq=o|Eew3pspY7NuDQ(Rsd#5?)NIt+ zPWh*+w2r&K>6(oh#5-oCh4a_3rss8Qz3F=i`j-Cy8mQL#nk7Z6HDyGVUOH_v^?sZz zb_5VWAQ1gn>~K#Cw0LbDZZzLr*b{WKH_p?b{6ON?ikkRCs<}_AG}F{-8RKIcXNWnv zwm+KToMDu@g!t#`%bSU$Vx;?a{=7J&ca>?J&1JO_-Y;()_|Knd{NBBePDMFNsy$!n zN78}4D)mc1wg4RM4fr?D;jLfA->BrNw8d9%qK<3LbDG#N*x>5;dNL9mA?jno>-C*`}<5lUFA7agW zJepV88-B@v0JMBMV4xh+ysLn|vdYoKeLPr}^?0iv|qs9`XKVHo6v96>%N zfix|~s$lLqbFG$WTAvGWVQLUD7#M^tOs8%�?r(Pt&y8G>1(=QUQ!o#@ecicqTp4 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∨DGIzqH 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 literal 0 HcmV?d00001 diff --git a/packages/auth-api/e2e/user/files/small.jpg b/packages/auth-api/e2e/user/files/small.jpg new file mode 100644 index 0000000000000000000000000000000000000000..eba421b87a79b5a33850b82cabc6c5e91b6d5ddf GIT binary patch literal 52721 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 { + let app: INestApplication; + let userService: UserService; + let authService: AuthService; + let roleService: RoleService; + let helperDateService: HelperDateService; + let authApiService: AuthApiService; + + const password = `@!${faker.name.firstName().toLowerCase()}${faker.name + .firstName() + .toUpperCase()}${faker.datatype.number({ min: 1, max: 99 })}`; + + const apiKey = 'qwertyuiop12345zxcvbnmkjh'; + let xApiKey: string; + let timestamp: number; + + let userData: Record; + let userExist: UserDocument; + + let accessToken: string; + + beforeAll(async () => { + const modRef = await Test.createTestingModule({ + imports: [ + CoreModule, + RouterAdminModule, + RouterModule.register([ + { + path: '/admin', + module: RouterAdminModule, + }, + ]), + ], + }).compile(); + + app = modRef.createNestApplication(); + useContainer(app.select(CoreModule), { fallbackOnErrors: true }); + userService = app.get(UserService); + authService = app.get(AuthService); + roleService = app.get(RoleService); + helperDateService = app.get(HelperDateService); + authApiService = app.get(AuthApiService); + + const role: RoleDocument = await roleService.findOne({ + name: 'user', + }); + + userData = { + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + password: password, + email: faker.internet.email(), + mobileNumber: faker.phone.number('62812#########'), + role: `${role._id}`, + }; + + const passwordHash = await authService.createPassword( + faker.internet.password(20, true, /[A-Za-z0-9]/) + ); + + userExist = await userService.create({ + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + password: passwordHash.passwordHash, + passwordExpired: passwordHash.passwordExpired, + salt: passwordHash.salt, + email: faker.internet.email(), + mobileNumber: faker.phone.number('62812#########'), + role: `${role._id}`, + }); + + const user = await userService.findOne( + { + email: 'admin@mail.com', + }, + { + populate: { + role: true, + permission: true, + }, + } + ); + + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadAccessToken(map, false); + accessToken = await authService.createAccessToken(payload); + + timestamp = helperDateService.timestamp(); + const apiEncryption = await authApiService.encryptApiKey( + { + key: apiKey, + timestamp, + hash: 'e11a023bc0ccf713cb50de9baa5140e59d3d4c52ec8952d9ca60326e040eda54', + }, + 'opbUwdiS1FBsrDUoPgZdx', + 'cuwakimacojulawu' + ); + xApiKey = `${apiKey}:${apiEncryption}`; + + await app.init(); + }); + + it(`GET ${E2E_USER_ADMIN_LIST_URL} List Success`, async () => { + const response = await request(app.getHttpServer()) + .get(E2E_USER_ADMIN_LIST_URL) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('Authorization', `Bearer ${accessToken}`) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`POST ${E2E_USER_ADMIN_CREATE_URL} Create, Error Request`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_USER_ADMIN_CREATE_URL) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + role: 'test_roles', + isAdmin: 'test', + }); + + expect(response.status).toEqual(HttpStatus.UNPROCESSABLE_ENTITY); + expect(response.body.statusCode).toEqual( + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_VALIDATION_ERROR + ); + + return; + }); + + it(`POST ${E2E_USER_ADMIN_CREATE_URL} Create, Role Not Found`, async () => { + const req = { + ...userData, + role: `${new Types.ObjectId()}`, + password, + }; + + const response = await request(app.getHttpServer()) + .post(E2E_USER_ADMIN_CREATE_URL) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send(req); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_ROLE_STATUS_CODE_ERROR.ROLE_NOT_FOUND_ERROR + ); + + return; + }); + + it(`POST ${E2E_USER_ADMIN_CREATE_URL} Create, Exist`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_USER_ADMIN_CREATE_URL) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + ...userData, + email: userExist.email, + mobileNumber: userExist.mobileNumber, + password, + }); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_EXISTS_ERROR + ); + + return; + }); + + it(`POST ${E2E_USER_ADMIN_CREATE_URL} Create, Email Exist`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_USER_ADMIN_CREATE_URL) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + ...userData, + email: userExist.email, + password, + }); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_EMAIL_EXIST_ERROR + ); + + return; + }); + + it(`POST ${E2E_USER_ADMIN_CREATE_URL} Create, Phone Number Exist`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_USER_ADMIN_CREATE_URL) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + ...userData, + mobileNumber: userExist.mobileNumber, + password, + }); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_MOBILE_NUMBER_EXIST_ERROR + ); + + return; + }); + + it(`POST ${E2E_USER_ADMIN_CREATE_URL} Create, Success`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_USER_ADMIN_CREATE_URL) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send(userData); + + userData = response.body.data; + expect(response.status).toEqual(HttpStatus.CREATED); + expect(response.body.statusCode).toEqual(HttpStatus.CREATED); + + return; + }); + + it(`GET ${E2E_USER_ADMIN_GET_URL} Get Not Found`, async () => { + const response = await request(app.getHttpServer()) + .get( + E2E_USER_ADMIN_GET_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR + ); + + return; + }); + + it(`GET ${E2E_USER_ADMIN_GET_URL} Get Success`, async () => { + const response = await request(app.getHttpServer()) + .get(E2E_USER_ADMIN_GET_URL.replace(':_id', userData._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`PUT ${E2E_USER_ADMIN_UPDATE_URL} Update, Error Request`, async () => { + const response = await request(app.getHttpServer()) + .put(E2E_USER_ADMIN_UPDATE_URL.replace(':_id', userData._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + firstName: [], + lastName: 1231231, + }) + .expect(422); + + expect(response.status).toEqual(HttpStatus.UNPROCESSABLE_ENTITY); + expect(response.body.statusCode).toEqual( + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_VALIDATION_ERROR + ); + + return; + }); + + it(`PUT ${E2E_USER_ADMIN_UPDATE_URL} Update, not found`, async () => { + const response = await request(app.getHttpServer()) + .put( + E2E_USER_ADMIN_UPDATE_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + }) + .expect(404); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR + ); + + return; + }); + + it(`PUT ${E2E_USER_ADMIN_UPDATE_URL} Update, success`, async () => { + const response = await request(app.getHttpServer()) + .put(E2E_USER_ADMIN_UPDATE_URL.replace(':_id', userData._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .send({ + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + }) + .expect(200); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`PATCH ${E2E_USER_ADMIN_INACTIVE_URL} Inactive, Not Found`, async () => { + const response = await request(app.getHttpServer()) + .patch( + E2E_USER_ADMIN_INACTIVE_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .expect(404); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_USER_ADMIN_INACTIVE_URL} Inactive, success`, async () => { + const response = await request(app.getHttpServer()) + .patch(E2E_USER_ADMIN_INACTIVE_URL.replace(':_id', userData._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .expect(200); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`PATCH ${E2E_USER_ADMIN_INACTIVE_URL} Inactive, already inactive`, async () => { + const response = await request(app.getHttpServer()) + .patch(E2E_USER_ADMIN_INACTIVE_URL.replace(':_id', userData._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .expect(400); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_ACTIVE_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_USER_ADMIN_ACTIVE_URL} Active, Not Found`, async () => { + const response = await request(app.getHttpServer()) + .patch( + E2E_USER_ADMIN_ACTIVE_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .expect(404); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR + ); + + return; + }); + + it(`PATCH ${E2E_USER_ADMIN_ACTIVE_URL} Active, success`, async () => { + const response = await request(app.getHttpServer()) + .patch(E2E_USER_ADMIN_ACTIVE_URL.replace(':_id', userData._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .expect(200); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`PATCH ${E2E_USER_ADMIN_ACTIVE_URL} Active, already active`, async () => { + const response = await request(app.getHttpServer()) + .patch(E2E_USER_ADMIN_ACTIVE_URL.replace(':_id', userData._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .expect(400); + + expect(response.status).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_ACTIVE_ERROR + ); + + return; + }); + + it(`DELETE ${E2E_USER_ADMIN_DELETE_URL} Delete, Not Found`, async () => { + const response = await request(app.getHttpServer()) + .delete( + E2E_USER_ADMIN_DELETE_URL.replace( + ':_id', + `${new Types.ObjectId()}` + ) + ) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .expect(404); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR + ); + + return; + }); + + it(`DELETE ${E2E_USER_ADMIN_DELETE_URL} Delete, success`, async () => { + const response = await request(app.getHttpServer()) + .delete(E2E_USER_ADMIN_DELETE_URL.replace(':_id', userData._id)) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey) + .expect(200); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + afterAll(async () => { + try { + await userService.deleteOneById(userData._id); + await userService.deleteOneById(userExist._id); + } catch (e) { + console.error(e); + } + + connection.close(); + await app.close(); + }); +}); diff --git a/packages/auth-api/e2e/user/user.constant.ts b/packages/auth-api/e2e/user/user.constant.ts new file mode 100644 index 0000000..5f5c772 --- /dev/null +++ b/packages/auth-api/e2e/user/user.constant.ts @@ -0,0 +1,10 @@ +export const E2E_USER_ADMIN_LIST_URL = '/admin/user/list'; +export const E2E_USER_ADMIN_GET_URL = '/admin/user/get/:_id'; +export const E2E_USER_ADMIN_ACTIVE_URL = '/admin/user/update/:_id/active'; +export const E2E_USER_ADMIN_INACTIVE_URL = '/admin/user/update/:_id/inactive'; +export const E2E_USER_ADMIN_CREATE_URL = '/admin/user/create'; +export const E2E_USER_ADMIN_UPDATE_URL = '/admin/user/update/:_id'; +export const E2E_USER_ADMIN_DELETE_URL = '/admin/user/delete/:_id'; + +export const E2E_USER_PUBLIC_PROFILE_URL = '/public/user/profile'; +export const E2E_USER_PUBLIC_PROFILE_UPLOAD_URL = '/public/user/profile/upload'; diff --git a/packages/auth-api/e2e/user/user.public.e2e-spec.ts b/packages/auth-api/e2e/user/user.public.e2e-spec.ts new file mode 100644 index 0000000..63fe922 --- /dev/null +++ b/packages/auth-api/e2e/user/user.public.e2e-spec.ts @@ -0,0 +1,232 @@ +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import request from 'supertest'; +import { faker } from '@faker-js/faker'; +import { IUserDocument } from 'src/user/user.interface'; +import { + E2E_USER_PUBLIC_PROFILE_UPLOAD_URL, + E2E_USER_PUBLIC_PROFILE_URL, +} from './user.constant'; +import { Types, connection } from 'mongoose'; +import { ENUM_USER_STATUS_CODE_ERROR } from 'src/user/user.constant'; +import { CoreModule } from 'src/core/core.module'; +import { RouterModule } from '@nestjs/core'; +import { UserService } from 'src/user/service/user.service'; +import { AuthService } from 'src/auth/service/auth.service'; +import { RoleService } from 'src/role/service/role.service'; +import { ENUM_FILE_STATUS_CODE_ERROR } from 'src/utils/file/file.constant'; +import { RouterPublicModule } from 'src/router/router.public.module'; +import { RoleDocument } from 'src/role/schema/role.schema'; +import { UserDocument } from 'src/user/schema/user.schema'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { useContainer } from 'class-validator'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; + +describe('E2E User Public', () => { + let app: INestApplication; + let userService: UserService; + let authService: AuthService; + let roleService: RoleService; + let helperDateService: HelperDateService; + let authApiService: AuthApiService; + + let user: UserDocument; + + let accessToken: string; + let accessTokenNotFound: string; + + const apiKey = 'qwertyuiop12345zxcvbnmkjh'; + let xApiKey: string; + let timestamp: number; + + beforeAll(async () => { + const modRef = await Test.createTestingModule({ + imports: [ + CoreModule, + RouterPublicModule, + RouterModule.register([ + { + path: '/public', + module: RouterPublicModule, + }, + ]), + ], + }).compile(); + + app = modRef.createNestApplication(); + useContainer(app.select(CoreModule), { fallbackOnErrors: true }); + userService = app.get(UserService); + authService = app.get(AuthService); + roleService = app.get(RoleService); + helperDateService = app.get(HelperDateService); + authApiService = app.get(AuthApiService); + + const role: RoleDocument = await roleService.findOne({ + name: 'user', + }); + + const passwordHash = await authService.createPassword( + faker.internet.password(20, true, /[A-Za-z0-9]/) + ); + + user = await userService.create({ + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + password: passwordHash.passwordHash, + passwordExpired: passwordHash.passwordExpired, + salt: passwordHash.salt, + email: faker.internet.email(), + mobileNumber: faker.phone.number('62812#########'), + role: `${role._id}`, + }); + + const userPopulate = await userService.findOneById( + user._id, + { + populate: { + role: true, + permission: true, + }, + } + ); + + const map = await authService.serializationLogin(userPopulate); + const payload = await authService.createPayloadAccessToken(map, false); + const payloadNotFound = { + ...payload, + _id: `${new Types.ObjectId()}`, + }; + + accessToken = await authService.createAccessToken(payload); + accessTokenNotFound = await authService.createAccessToken( + payloadNotFound + ); + + timestamp = helperDateService.timestamp(); + const apiEncryption = await authApiService.encryptApiKey( + { + key: apiKey, + timestamp, + hash: 'e11a023bc0ccf713cb50de9baa5140e59d3d4c52ec8952d9ca60326e040eda54', + }, + 'opbUwdiS1FBsrDUoPgZdx', + 'cuwakimacojulawu' + ); + xApiKey = `${apiKey}:${apiEncryption}`; + + await app.init(); + }); + + it(`GET ${E2E_USER_PUBLIC_PROFILE_URL} Profile Not Found`, async () => { + const response = await request(app.getHttpServer()) + .get(E2E_USER_PUBLIC_PROFILE_URL) + .set('Authorization', `Bearer ${accessTokenNotFound}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR + ); + + return; + }); + + it(`GET ${E2E_USER_PUBLIC_PROFILE_URL} Profile`, async () => { + const response = await request(app.getHttpServer()) + .get(E2E_USER_PUBLIC_PROFILE_URL) + .set('Authorization', `Bearer ${accessToken}`) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + it(`POST ${E2E_USER_PUBLIC_PROFILE_UPLOAD_URL} Profile Upload Error Request`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_USER_PUBLIC_PROFILE_UPLOAD_URL) + .attach('file', './e2e/user/files/test.txt') + .set('Authorization', `Bearer ${accessToken}`) + .set('Content-Type', 'multipart/form-data') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.UNSUPPORTED_MEDIA_TYPE); + expect(response.body.statusCode).toEqual( + ENUM_FILE_STATUS_CODE_ERROR.FILE_EXTENSION_ERROR + ); + + return; + }); + + it(`POST ${E2E_USER_PUBLIC_PROFILE_UPLOAD_URL} Profile Upload Not Found`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_USER_PUBLIC_PROFILE_UPLOAD_URL) + .attach('file', './e2e/user/files/test.txt') + .set('Authorization', `Bearer ${accessTokenNotFound}`) + .set('Content-Type', 'multipart/form-data') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.NOT_FOUND); + expect(response.body.statusCode).toEqual( + ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR + ); + + return; + }); + + it(`POST ${E2E_USER_PUBLIC_PROFILE_UPLOAD_URL} Profile Upload File Too Large`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_USER_PUBLIC_PROFILE_UPLOAD_URL) + .send() + .attach('file', './e2e/user/files/medium.jpg') + .set('Authorization', `Bearer ${accessToken}`) + .set('Content-Type', 'multipart/form-data') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.PAYLOAD_TOO_LARGE); + expect(response.body.statusCode).toEqual( + ENUM_FILE_STATUS_CODE_ERROR.FILE_MAX_SIZE_ERROR + ); + + return; + }); + + it(`POST ${E2E_USER_PUBLIC_PROFILE_UPLOAD_URL} Profile Upload Success`, async () => { + const response = await request(app.getHttpServer()) + .post(E2E_USER_PUBLIC_PROFILE_UPLOAD_URL) + .send() + .attach('file', './e2e/user/files/small.jpg') + .set('Authorization', `Bearer ${accessToken}`) + .set('Content-Type', 'multipart/form-data') + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }, 10000); + + afterAll(async () => { + try { + await userService.deleteOneById(user._id); + } catch (e) { + console.error(e); + } + + connection.close(); + await app.close(); + }); +}); diff --git a/packages/auth-api/endpoints/endpoints.json b/packages/auth-api/endpoints/endpoints.json new file mode 100644 index 0000000..e6e97ac --- /dev/null +++ b/packages/auth-api/endpoints/endpoints.json @@ -0,0 +1,7122 @@ +{ + "info": { + "_postman_id": "57d63008-b9ba-4d0b-8213-d7defe1b3563", + "name": "ac.k", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "5490397" + }, + "item": [ + { + "name": "test", + "item": [ + { + "name": "hello", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "// Set Function", + "const cryptoJs = require('crypto-js');", + "const { AES, enc, mode, pad } = cryptoJs;", + "const setUtils = {", + " getCurrentTimestamp: function() {", + " return Date.now();", + " },", + " createXApiKey: function (data, key, iv){", + " const cIv = enc.Utf8.parse(iv);", + " const cipher = AES.encrypt(", + " JSON.stringify(data), ", + " key, ", + " {", + " mode: mode.CBC,", + " padding: pad.Pkcs7,", + " iv: cIv,", + " }", + " );", + "", + " return cipher.toString();", + " }", + "}", + "pm.globals.set('utils', setUtils);", + "let utils = eval(pm.globals.get(\"utils\"));", + "", + "// Set x-timestamp", + "pm.environment.set(\"xTimestamp\", utils.getCurrentTimestamp());", + "", + "// Set x-api-key", + "const xTimestamp = pm.environment.get(\"xTimestamp\");", + "const apiKey = pm.environment.get(\"ApiKey\");", + "const apiHash = pm.environment.get(\"ApiHash\");", + "const apiEncryptionKey = pm.environment.get(\"ApiEncryptionKey\");", + "const apiPassphrase = pm.environment.get(\"ApiPassphrase\");", + "const apiPayload = {", + " \"key\": apiKey,", + " \"timestamp\": parseInt(xTimestamp),", + " \"hash\": apiHash", + "}", + "const apiEncrypted = utils.createXApiKey(apiPayload, apiEncryptionKey, apiPassphrase);", + "const xApiKey = `${apiKey}:${apiEncrypted}`;", + "pm.environment.set(\"ApiEncrypted\", apiEncrypted);", + "pm.environment.set(\"xApiKey\", xApiKey);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-custom-lang", + "value": "en,id", + "type": "text" + }, + { + "key": "x-timezone", + "value": "Asia/Jakarta", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/test/hello", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "test", + "hello" + ] + } + }, + "response": [ + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + }, + { + "key": "x-custom-lang", + "value": "en", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/test/hello", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "test", + "hello" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"This is test endpoint.\",\n \"data\": {\n \"userAgent\": {\n \"ua\": \"PostmanRuntime/7.29.0\",\n \"browser\": {},\n \"engine\": {},\n \"os\": {},\n \"device\": {},\n \"cpu\": {}\n },\n \"date\": \"2022-06-17T17:20:13.018Z\",\n \"format\": \"2022-06-18T00:20:13+07:00\",\n \"timestamp\": 1655486413018\n }\n}" + }, + { + "name": "success multi language", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + }, + { + "key": "x-custom-lang", + "value": "en,id", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/test/hello", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "test", + "hello" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": {\n \"en\": \"This is test endpoint.\",\n \"id\": \"Ini adalah endpoint test\"\n },\n \"data\": {\n \"userAgent\": {\n \"ua\": \"PostmanRuntime/7.29.0\",\n \"browser\": {},\n \"engine\": {},\n \"os\": {},\n \"device\": {},\n \"cpu\": {}\n },\n \"date\": \"2022-06-17T17:20:13.018Z\",\n \"format\": \"2022-06-18T00:20:13+07:00\",\n \"timestamp\": 1655486413018\n }\n}" + } + ] + }, + { + "name": "hello timeout", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "// Set Function", + "const cryptoJs = require('crypto-js');", + "const { AES, enc, mode, pad } = cryptoJs;", + "const setUtils = {", + " getCurrentTimestamp: function() {", + " return Date.now();", + " },", + " createXApiKey: function (data, key, iv){", + " const cIv = enc.Utf8.parse(iv);", + " const cipher = AES.encrypt(", + " JSON.stringify(data), ", + " key, ", + " {", + " mode: mode.CBC,", + " padding: pad.Pkcs7,", + " iv: cIv,", + " }", + " );", + "", + " return cipher.toString();", + " }", + "}", + "pm.globals.set('utils', setUtils);", + "let utils = eval(pm.globals.get(\"utils\"));", + "", + "// Set x-timestamp", + "pm.environment.set(\"xTimestamp\", utils.getCurrentTimestamp());", + "", + "// Set x-api-key", + "const xTimestamp = pm.environment.get(\"xTimestamp\");", + "const apiKey = pm.environment.get(\"ApiKey\");", + "const apiHash = pm.environment.get(\"ApiHash\");", + "const apiEncryptionKey = pm.environment.get(\"ApiEncryptionKey\");", + "const apiPassphrase = pm.environment.get(\"ApiPassphrase\");", + "const apiPayload = {", + " \"key\": apiKey,", + " \"timestamp\": parseInt(xTimestamp),", + " \"hash\": apiHash", + "}", + "const apiEncrypted = utils.createXApiKey(apiPayload, apiEncryptionKey, apiPassphrase);", + "const xApiKey = `${apiKey}:${apiEncrypted}`;", + "pm.environment.set(\"ApiEncrypted\", apiEncrypted);", + "pm.environment.set(\"xApiKey\", xApiKey);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-custom-lang", + "value": "en", + "type": "text" + }, + { + "key": "x-timezone", + "value": "Asia/Jakarta", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/test/hello-timeout", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "test", + "hello-timeout" + ] + } + }, + "response": [ + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + }, + { + "key": "x-custom-lang", + "value": "en", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/test/hello", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "test", + "hello" + ] + } + }, + "status": "Request Timeout", + "code": 408, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5992,\n \"message\": \"Request time out\"\n}" + } + ] + } + ] + }, + { + "name": "enum", + "item": [ + { + "name": "languages", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/enum/message/languages", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "enum", + "message", + "languages" + ] + } + }, + "response": [ + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/enum/message/languages", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "enum", + "message", + "languages" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"message enum languages\",\n \"data\": {\n \"languages\": [\n \"EN\",\n \"ID\"\n ]\n }\n}" + } + ] + } + ] + }, + { + "name": "health check", + "item": [ + { + "name": "aws", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/health/aws", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "health", + "aws" + ] + } + }, + "response": [ + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/health/aws", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "health", + "aws" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Healthy\",\n \"data\": {\n \"status\": \"ok\",\n \"info\": {\n \"awsBucket\": {\n \"status\": \"up\"\n }\n },\n \"error\": {},\n \"details\": {\n \"awsBucket\": {\n \"status\": \"up\"\n }\n }\n }\n}" + } + ] + }, + { + "name": "database", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/health/database", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "health", + "database" + ] + } + }, + "response": [ + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/health/database", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "health", + "database" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Healthy\",\n \"data\": {\n \"status\": \"ok\",\n \"info\": {\n \"aws bucket\": {\n \"status\": \"up\"\n }\n },\n \"error\": {},\n \"details\": {\n \"aws bucket\": {\n \"status\": \"up\"\n }\n }\n }\n}" + } + ] + }, + { + "name": "memory heap", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/health/memory-heap", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "health", + "memory-heap" + ] + } + }, + "response": [ + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/health/memory-heap", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "health", + "memory-heap" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Healthy\",\n \"data\": {\n \"status\": \"ok\",\n \"info\": {\n \"memoryHeap\": {\n \"status\": \"up\"\n }\n },\n \"error\": {},\n \"details\": {\n \"memoryHeap\": {\n \"status\": \"up\"\n }\n }\n }\n}" + } + ] + }, + { + "name": "memory rss", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/health/memory-rss", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "health", + "memory-rss" + ] + } + }, + "response": [ + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/health/memory-rss", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "health", + "memory-rss" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Healthy\",\n \"data\": {\n \"status\": \"ok\",\n \"info\": {\n \"memoryRss\": {\n \"status\": \"up\"\n }\n },\n \"error\": {},\n \"details\": {\n \"memoryRss\": {\n \"status\": \"up\"\n }\n }\n }\n}" + } + ] + }, + { + "name": "storage", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/health/storage", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "health", + "storage" + ] + } + }, + "response": [ + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}/health/storage", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}" + ], + "path": [ + "health", + "storage" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Healthy\",\n \"data\": {\n \"status\": \"ok\",\n \"info\": {\n \"storage\": {\n \"status\": \"up\"\n }\n },\n \"error\": {},\n \"details\": {\n \"storage\": {\n \"status\": \"up\"\n }\n }\n }\n}" + } + ] + } + ] + }, + { + "name": "authenticated", + "item": [ + { + "name": "public", + "item": [ + { + "name": "profile", + "item": [ + { + "name": "profile", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/user/profile", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "user", + "profile" + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/user/profile", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "user", + "profile" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5400,\n \"message\": \"User not found.\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/user/profile", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "user", + "profile" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Profile Success\",\n \"data\": {\n \"_id\": \"6298d44cf50e591f9369e2e8\",\n \"firstName\": \"user@mail.com\",\n \"lastName\": \"aaa\",\n \"mobileNumber\": \"628123121111\",\n \"email\": \"user@mail.com\",\n \"role\": {\n \"name\": \"user\",\n \"permissions\": [],\n \"isActive\": true\n },\n \"passwordExpired\": \"2022-12-01T15:16:28.657Z\",\n \"isActive\": true\n }\n}" + } + ] + }, + { + "name": "upload", + "request": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "/Users/andrechristikan/Downloads/Unknown.jpg" + } + ] + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/user/profile/upload", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "user", + "profile", + "upload" + ] + } + }, + "response": [ + { + "name": "file not found", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "multipart/form-data", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "/Users/andrechristikan/Downloads/Unknown.jpg" + } + ] + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/user/profile/upload", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "user", + "profile", + "upload" + ] + } + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5950,\n \"message\": \"File not found\"\n}" + }, + { + "name": "unsupport mime", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "multipart/form-data", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": [] + } + ] + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/user/profile/upload", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "user", + "profile", + "upload" + ] + } + }, + "status": "Unsupported Media Type", + "code": 415, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5952,\n \"message\": \"File extension not valid\"\n}" + }, + { + "name": "size too big", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "multipart/form-data", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": [] + } + ] + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/user/profile/upload", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "user", + "profile", + "upload" + ] + } + }, + "status": "Request Entity Too Large", + "code": 413, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5951,\n \"message\": \"File size too big\"\n}" + }, + { + "name": "not found", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "multipart/form-data", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": [] + } + ] + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/user/profile/upload", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "user", + "profile", + "upload" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5400,\n \"message\": \"User not found.\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "multipart/form-data", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "/Users/andrechristikan/Downloads/snowshoe.jpg" + } + ] + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/user/profile/upload", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "user", + "profile", + "upload" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Upload Success\"\n}" + } + ] + } + ] + }, + { + "name": "sign up", + "request": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"user@mail.com\",\n \"mobileNumber\": \"628123121111\",\n \"firstName\": \"test\",\n \"lastName\": \"aaa\",\n \"password\": \"aaAA@@123444\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/auth/sign-up", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "auth", + "sign-up" + ] + } + }, + "response": [ + { + "name": "error request", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"andreck@mail.com\",\n \"mobileNumber\": \"628123123311\",\n \"firstName\": \"test\",\n \"lastName\": \"aaa\",\n \"password\": \"aaAA@@123444\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/auth/sign-up", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "auth", + "sign-up" + ] + } + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5981,\n \"message\": \"Unprocessable Entity\",\n \"errors\": [\n {\n \"property\": \"email\",\n \"message\": \"email should be a type of email.\"\n }\n ]\n}" + }, + { + "name": "not found", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"andreck@mail.com\",\n \"mobileNumber\": \"628123123311\",\n \"firstName\": \"test\",\n \"lastName\": \"aaa\",\n \"password\": \"aaAA@@123444\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/auth/sign-up", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "auth", + "sign-up" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5501,\n \"message\": \"Role not found.\"\n}" + }, + { + "name": "user exist", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"andreck@mail.com\",\n \"mobileNumber\": \"628123123311\",\n \"firstName\": \"test\",\n \"lastName\": \"aaa\",\n \"password\": \"aaAA@@123444\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/auth/sign-up", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "auth", + "sign-up" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5401,\n \"message\": \"User exist.\"\n}" + }, + { + "name": "email exist", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"andreck@mail.com\",\n \"mobileNumber\": \"628123123311\",\n \"firstName\": \"test\",\n \"lastName\": \"aaa\",\n \"password\": \"aaAA@@123444\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/auth/sign-up", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "auth", + "sign-up" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5403,\n \"message\": \"Email user used\"\n}" + }, + { + "name": "mobile number exist", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"andreck@mail.com\",\n \"mobileNumber\": \"628123123311\",\n \"firstName\": \"test\",\n \"lastName\": \"aaa\",\n \"password\": \"aaAA@@123444\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/auth/sign-up", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "auth", + "sign-up" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5404,\n \"message\": \"Mobile Number user used\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"andreck@mail.com\",\n \"mobileNumber\": \"628123123311\",\n \"firstName\": \"test\",\n \"lastName\": \"aaa\",\n \"password\": \"aaAA@@123444\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/public/auth/sign-up", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "public", + "auth", + "sign-up" + ] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 201,\n \"message\": \"Sign up Success\",\n \"data\": {\n \"accessToken\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2Mjk4ZDQ0Y2Y1MGU1OTFmOTM2OWUyZTgiLCJtb2JpbGVOdW1iZXIiOiI2MjgxMjMxMjExMTEiLCJlbWFpbCI6InVzZXJAbWFpbC5jb20iLCJyb2xlIjp7Im5hbWUiOiJ1c2VyIiwicGVybWlzc2lvbnMiOltdLCJpc0FjdGl2ZSI6dHJ1ZSwiaXNBZG1pbiI6ZmFsc2V9LCJwYXNzd29yZEV4cGlyZWQiOiIyMDIyLTEyLTAxVDE1OjE2OjI4LjY1N1oiLCJpc0FjdGl2ZSI6dHJ1ZSwicmVtZW1iZXJNZSI6ZmFsc2UsImxvZ2luRGF0ZSI6IjIwMjItMDYtMDJUMTU6MTY6MjguNzAwWiIsImlhdCI6MTY1NDE4Mjk4OCwibmJmIjoxNjU0MTgyOTg4LCJleHAiOjE2NTQxODQ3ODh9.sSUTgrv0dCa9cRLlg7qemHs31vQXRGSgwAQJuaf1yMw\",\n \"refreshToken\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2Mjk4ZDQ0Y2Y1MGU1OTFmOTM2OWUyZTgiLCJyZW1lbWJlck1lIjpmYWxzZSwibG9naW5EYXRlIjoiMjAyMi0wNi0wMlQxNToxNjoyOC43MDBaIiwiaWF0IjoxNjU0MTgyOTg4LCJuYmYiOjE2NTQxODQ3ODgsImV4cCI6MTY1NDc4Nzc4OH0.0PH0R_JLRtRc2GEjwC-27ygmrPy_kUKBBrZ-RBMJO04\"\n }\n}" + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + }, + { + "name": "admin", + "item": [ + { + "name": "user", + "item": [ + { + "name": "list", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/list?page=1&perPage=10&sort=name@asc&search", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "list" + ], + "query": [ + { + "key": "page", + "value": "1", + "description": "max 20" + }, + { + "key": "perPage", + "value": "10", + "description": "max 100" + }, + { + "key": "sort", + "value": "name@asc", + "description": "firstName,lastName,email,phoneNumber,createdAt" + }, + { + "key": "search", + "value": null, + "description": "firstName,lastName,email,phoneNumber" + } + ] + } + }, + "response": [ + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/list?page=1&perPage=10&sort=name@asc&search", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "list" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "perPage", + "value": "10" + }, + { + "key": "sort", + "value": "name@asc" + }, + { + "key": "search", + "value": null + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"List User Success.\",\n \"totalData\": 2,\n \"totalPage\": 1,\n \"currentPage\": 1,\n \"perPage\": 10,\n \"availableSort\": [\n \"firstName\",\n \"lastName\",\n \"email\",\n \"phoneNumber\",\n \"createdAt\"\n ],\n \"availableSearch\": [\n \"firstName\",\n \"lastName\",\n \"email\",\n \"phoneNumber\"\n ],\n \"data\": [\n {\n \"_id\": \"6298cb32bd530c9c84266c40\",\n \"firstName\": \"admin@mail.com\",\n \"lastName\": \"test\",\n \"mobileNumber\": \"08111111111\",\n \"email\": \"admin@mail.com\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T14:37:38.729Z\"\n },\n {\n \"_id\": \"6298d44cf50e591f9369e2e8\",\n \"firstName\": \"user@mail.com\",\n \"lastName\": \"aaa\",\n \"mobileNumber\": \"628123121111\",\n \"email\": \"user@mail.com\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T15:16:28.685Z\"\n }\n ]\n}" + } + ] + }, + { + "name": "get", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/get/:user", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "get", + ":user" + ], + "variable": [ + { + "key": "user", + "value": "6298cb32bd530c9c84266c40" + } + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/get/:user", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "get", + ":user" + ], + "variable": [ + { + "key": "user", + "value": "61c047c9567c3319208dbe87" + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5400,\n \"message\": \"User not found.\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/get/:user", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "get", + ":user" + ], + "variable": [ + { + "key": "user", + "value": "61c047c9567c3319208dbe87" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Get User Success.\",\n \"data\": {\n \"_id\": \"6298cb32bd530c9c84266c40\",\n \"firstName\": \"admin@mail.com\",\n \"lastName\": \"test\",\n \"mobileNumber\": \"08111111111\",\n \"email\": \"admin@mail.com\",\n \"role\": {\n \"name\": \"admin\",\n \"permissions\": [\n {\n \"name\": \"permission read\",\n \"isActive\": true\n },\n {\n \"name\": \"role create\",\n \"isActive\": true\n },\n {\n \"name\": \"permission test\",\n \"isActive\": true\n },\n {\n \"name\": \"role read\",\n \"isActive\": true\n },\n {\n \"name\": \"role update\",\n \"isActive\": true\n },\n {\n \"name\": \"setting read\",\n \"isActive\": true\n },\n {\n \"name\": \"setting update\",\n \"isActive\": true\n },\n {\n \"name\": \"user create\",\n \"isActive\": true\n },\n {\n \"name\": \"user delete\",\n \"isActive\": false\n },\n {\n \"name\": \"user read\",\n \"isActive\": true\n },\n {\n \"name\": \"user update\",\n \"isActive\": true\n }\n ],\n \"isActive\": true\n },\n \"passwordExpired\": \"2022-12-01T14:37:38.705Z\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T14:37:38.729Z\"\n }\n}" + } + ] + }, + { + "name": "create", + "request": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"andre@mail.com\",\n \"firstName\": \"andre\",\n \"lastName\": \"ck\",\n \"mobileNumber\": \"6281321312212\",\n \"password\": \"aa@AA123456\",\n \"role\": \"6298cb290280eeabb17f9d33\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/create", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "create" + ] + } + }, + "response": [ + { + "name": "error request", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"andreck@mail.com\",\n \"firstName\": \"andre\",\n \"lastName\": \"ck\",\n \"mobileNumber\": \"6281321312212\",\n \"password\": \"aa@AA123456\",\n \"role\": \"61572e9b44c5132c14f6a5fe\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/create", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "create" + ] + } + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5981,\n \"message\": \"Unprocessable Entity\",\n \"errors\": [\n {\n \"property\": \"role\",\n \"message\": \"role should reference with mongo object id.\"\n }\n ]\n}" + }, + { + "name": "exist", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"andreck@mail.com\",\n \"firstName\": \"andre\",\n \"lastName\": \"ck\",\n \"mobileNumber\": \"6281321312212\",\n \"password\": \"aa@AA123456\",\n \"role\": \"61572e9b44c5132c14f6a5fe\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/create", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "create" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5401,\n \"message\": \"User exist.\"\n}" + }, + { + "name": "email exist", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"andreck@mail.com\",\n \"firstName\": \"andre\",\n \"lastName\": \"ck\",\n \"mobileNumber\": \"6281321312212\",\n \"password\": \"aa@AA123456\",\n \"role\": \"61572e9b44c5132c14f6a5fe\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/create", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "create" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5403,\n \"message\": \"Email user used\"\n}" + }, + { + "name": "mobile number exist", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"andreck@mail.com\",\n \"firstName\": \"andre\",\n \"lastName\": \"ck\",\n \"mobileNumber\": \"6281321312212\",\n \"password\": \"aa@AA123456\",\n \"role\": \"61572e9b44c5132c14f6a5fe\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/create", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "create" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5404,\n \"message\": \"Mobile Number user used\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"andreck@mail.com\",\n \"firstName\": \"andre\",\n \"lastName\": \"ck\",\n \"mobileNumber\": \"6281321312212\",\n \"password\": \"aa@AA123456\",\n \"role\": \"61572e9b44c5132c14f6a5fe\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/create", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "create" + ] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 201,\n \"message\": \"Create User Success.\",\n \"data\": {\n \"_id\": \"6298e254f50e591f9369e3ee\"\n }\n}" + } + ] + }, + { + "name": "delete", + "request": { + "method": "DELETE", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/delete/:user", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "delete", + ":user" + ], + "variable": [ + { + "key": "user", + "value": "6298e254f50e591f9369e3ee" + } + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/delete/:user", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "delete", + ":user" + ], + "variable": [ + { + "key": "user", + "value": "61e3bc3e911d2d7f3c5d43b0" + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5400,\n \"message\": \"User not found.\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/delete/:user", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "delete", + ":user" + ], + "variable": [ + { + "key": "user", + "value": "61c047c9567c3319208dbe87" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Delete User Success.\"\n}" + } + ] + }, + { + "name": "update", + "request": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"asdasda\",\n \"lastName\": \"kan\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/update/:user", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "update", + ":user" + ], + "variable": [ + { + "key": "user", + "value": "6298d44cf50e591f9369e2e8" + } + ] + } + }, + "response": [ + { + "name": "error request", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": [],\n \"lastName\": \"kan\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/update/:user", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "update", + ":user" + ], + "variable": [ + { + "key": "user", + "value": "61c047c9567c3319208dbe87" + } + ] + } + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5981,\n \"message\": \"Unprocessable Entity\",\n \"errors\": [\n {\n \"property\": \"firstName\",\n \"message\": \"firstName has more elements than the maximum allowed.\"\n },\n {\n \"property\": \"firstName\",\n \"message\": \"firstName should be a type of string.\"\n }\n ]\n}" + }, + { + "name": "not found", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/update/:user", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "update", + ":user" + ], + "variable": [ + { + "key": "user", + "value": "61c047c9567c3319208dbe87" + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5400,\n \"message\": \"User not found.\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"andre\",\n \"lastName\": \"kan\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/update/:user", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "update", + ":user" + ], + "variable": [ + { + "key": "user", + "value": "61c047c9567c3319208dbe87" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Update User Success.\",\n \"data\": {\n \"_id\": \"6298d44cf50e591f9369e2e8\"\n }\n}" + } + ] + }, + { + "name": "inactive", + "request": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/update/:user/inactive", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "update", + ":user", + "inactive" + ], + "variable": [ + { + "key": "user", + "value": null + } + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/update/:user/inactive", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "update", + ":user", + "inactive" + ], + "variable": [ + { + "key": "user", + "value": null + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5400,\n \"message\": \"User not found\"\n}" + }, + { + "name": "aready inactive", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/update/:user/inactive", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "update", + ":user", + "inactive" + ], + "variable": [ + { + "key": "user", + "value": null + } + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5405,\n \"message\": \"User status active invalid\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/update/:user/inactive", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "update", + ":user", + "inactive" + ], + "variable": [ + { + "key": "user", + "value": null + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Inactive Succeed\"\n}" + } + ] + }, + { + "name": "active", + "request": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/update/:user/active", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "update", + ":user", + "active" + ], + "variable": [ + { + "key": "user", + "value": null + } + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/update/:user/active", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "update", + ":user", + "active" + ], + "variable": [ + { + "key": "user", + "value": null + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5400,\n \"message\": \"User not found\"\n}" + }, + { + "name": "already active", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/update/:user/active", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "update", + ":user", + "active" + ], + "variable": [ + { + "key": "user", + "value": null + } + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5405,\n \"message\": \"User status active invalid\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/user/update/:user/active", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "user", + "update", + ":user", + "active" + ], + "variable": [ + { + "key": "user", + "value": null + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Active Succeed\"\n}" + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + }, + { + "name": "role", + "item": [ + { + "name": "list", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/list?page=1&perPage=10&sort=name@asc&search=", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "list" + ], + "query": [ + { + "key": "page", + "value": "1", + "description": "max 20" + }, + { + "key": "perPage", + "value": "10", + "description": "max 100" + }, + { + "key": "sort", + "value": "name@asc", + "description": "name,createdAt" + }, + { + "key": "search", + "value": "", + "description": "name" + } + ] + } + }, + "response": [ + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/list?page=1&perPage=10&sort=name@asc&search=", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "list" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "perPage", + "value": "10" + }, + { + "key": "sort", + "value": "name@asc" + }, + { + "key": "search", + "value": "" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"List Role Success.\",\n \"totalData\": 2,\n \"totalPage\": 1,\n \"currentPage\": 1,\n \"perPage\": 10,\n \"availableSort\": [\n \"name\",\n \"createdAt\"\n ],\n \"availableSearch\": [\n \"name\"\n ],\n \"data\": [\n {\n \"_id\": \"6298cb290280eeabb17f9d32\",\n \"name\": \"admin\",\n \"isActive\": true,\n \"isAdmin\": true,\n \"createdAt\": \"2022-06-02T14:37:29.771Z\"\n },\n {\n \"_id\": \"6298cb290280eeabb17f9d33\",\n \"name\": \"user\",\n \"isActive\": true,\n \"isAdmin\": false,\n \"createdAt\": \"2022-06-02T14:37:29.772Z\"\n }\n ]\n}" + } + ] + }, + { + "name": "get", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/get/:role", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "get", + ":role" + ], + "variable": [ + { + "key": "role", + "value": "6298cb290280eeabb17f9d32" + } + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/get/:role", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "get", + ":role" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5501,\n \"message\": \"Role not found.\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/get/:role", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "get", + ":role" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Get Role Success.\",\n \"data\": {\n \"_id\": \"6298cb290280eeabb17f9d32\",\n \"name\": \"admin\",\n \"permissions\": [\n {\n \"_id\": \"6298cb21094ec30dfa5d08bb\",\n \"code\": \"PERMISSION_READ\",\n \"name\": \"permission read\",\n \"isActive\": true\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08b7\",\n \"code\": \"ROLE_CREATE\",\n \"name\": \"role create\",\n \"isActive\": true\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08ba\",\n \"code\": \"ROLE_DELETE\",\n \"name\": \"permission test\",\n \"isActive\": true\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08b9\",\n \"code\": \"ROLE_READ\",\n \"name\": \"role read\",\n \"isActive\": true\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08b8\",\n \"code\": \"ROLE_UPDATE\",\n \"name\": \"role update\",\n \"isActive\": true\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08bd\",\n \"code\": \"SETTING_READ\",\n \"name\": \"setting read\",\n \"isActive\": true\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08be\",\n \"code\": \"SETTING_UPDATE\",\n \"name\": \"setting update\",\n \"isActive\": true\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08b3\",\n \"code\": \"USER_CREATE\",\n \"name\": \"user create\",\n \"isActive\": true\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08b6\",\n \"code\": \"USER_DELETE\",\n \"name\": \"user delete\",\n \"isActive\": false\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08b5\",\n \"code\": \"USER_READ\",\n \"name\": \"user read\",\n \"isActive\": true\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08b4\",\n \"code\": \"USER_UPDATE\",\n \"name\": \"user update\",\n \"isActive\": true\n }\n ],\n \"isActive\": true,\n \"isAdmin\": true,\n \"createdAt\": \"2022-06-02T14:37:29.771Z\"\n }\n}" + } + ] + }, + { + "name": "create", + "request": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"RRQasdass\",\n \"permissions\": [\n \"6298cb21094ec30dfa5d081b\"\n ],\n \"isAdmin\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/create", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "create" + ] + } + }, + "response": [ + { + "name": "error request", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"user\",\n \"permissions\": [\n \"61572e95439bca2c0f28d9aa\",\n \"61572e95439bca2c0f28d9a9\"\n ],\n \"isAdmin\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/create", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "create" + ] + } + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5981,\n \"message\": \"Unprocessable Entity\",\n \"errors\": [\n {\n \"property\": \"isAdmin\",\n \"message\": \"isAdmin should be a boolean\"\n }\n ]\n}" + }, + { + "name": "exists", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"user\",\n \"permissions\": [\n \"61572e95439bca2c0f28d9aa\",\n \"61572e95439bca2c0f28d9a9\"\n ],\n \"isAdmin\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/create", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "create" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5502,\n \"message\": \"Role exist\"\n}" + }, + { + "name": "permission not found", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"user\",\n \"permissions\": [\n \"61572e95439bca2c0f28d9aa\",\n \"61572e95439bca2c0f28d9a9\"\n ],\n \"isAdmin\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/create", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "create" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5200,\n \"message\": \"Permission not found\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"user\",\n \"permissions\": [\n \"61572e95439bca2c0f28d9aa\",\n \"61572e95439bca2c0f28d9a9\"\n ],\n \"isAdmin\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/create", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "create" + ] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 201,\n \"message\": \"Create Succeed\",\n \"data\": {\n \"_id\": \"6298deeaf50e591f9369e37c\"\n }\n}" + } + ] + }, + { + "name": "delete", + "request": { + "method": "DELETE", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/delete/:role", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "delete", + ":role" + ], + "variable": [ + { + "key": "role", + "value": "6298deeaf50e591f9369e37c" + } + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/delete/:role", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "delete", + ":role" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5501,\n \"message\": \"Role not found\"\n}" + }, + { + "name": "in used", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/delete/:role", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "delete", + ":role" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5504,\n \"message\": \"Role in used\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/delete/:role", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "delete", + ":role" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Delete Succeed\"\n}" + } + ] + }, + { + "name": "update", + "request": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"user\",\n \"permissions\": [\n \"6298cb21094ec30dfa5d08bb\"\n ],\n \"isAdmin\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role" + ], + "variable": [ + { + "key": "role", + "value": "6298cb290280eeabb17f9d31" + } + ] + } + }, + "response": [ + { + "name": "error request", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"user\",\n \"permissions\": [\n \"61572e95439d9aa\"\n ],\n \"isAdmin\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5981,\n \"message\": \"Unprocessable Entity\",\n \"errors\": [\n {\n \"property\": \"permissions\",\n \"message\": \"permissions should reference with mongo object id.\"\n }\n ]\n}" + }, + { + "name": "not found", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"user\",\n \"permissions\": [\n \"61d6fafa5a9afd84238e65d3\"\n ],\n \"isAdmin\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5501,\n \"message\": \"Role not found\"\n}" + }, + { + "name": "exists", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"user\",\n \"permissions\": [\n \"61572e95439bca2c0f28d9aa\",\n \"61572e95439bca2c0f28d9a9\"\n ],\n \"isAdmin\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5502,\n \"message\": \"Role exist\"\n}" + }, + { + "name": "permission not found", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"user\",\n \"permissions\": [\n \"61572e95439bca2c0f28d9aa\",\n \"61572e95439bca2c0f28d9a9\"\n ],\n \"isAdmin\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5200,\n \"message\": \"Permission not found\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"user\",\n \"permissions\": [\n \"61d6fafa5a9afd84238e65d3\"\n ],\n \"isAdmin\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Update Succeed\",\n \"data\": {\n \"_id\": \"6298cb290280eeabb17f9d33\"\n }\n}" + } + ] + }, + { + "name": "inactive", + "request": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role/inactive", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role", + "inactive" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role/inactive", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role", + "inactive" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5501,\n \"message\": \"Role not found\"\n}" + }, + { + "name": "aready inactive", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role/inactive", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role", + "inactive" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5503,\n \"message\": \"Role status active invalid\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role/inactive", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role", + "inactive" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Inactive Succeed\"\n}" + } + ] + }, + { + "name": "active", + "request": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role/active", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role", + "active" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role/active", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role", + "active" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5501,\n \"message\": \"Role not found\"\n}" + }, + { + "name": "already active", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role/active", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role", + "active" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5503,\n \"message\": \"Role status active invalid\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/role/update/:role/active", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "role", + "update", + ":role", + "active" + ], + "variable": [ + { + "key": "role", + "value": null + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Active Succeed\"\n}" + } + ] + } + ] + }, + { + "name": "permission", + "item": [ + { + "name": "list", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/list?perPage=10&page=1&sort=name@asc&search=&isActive=true,false", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "list" + ], + "query": [ + { + "key": "perPage", + "value": "10", + "description": "max 100" + }, + { + "key": "page", + "value": "1", + "description": "max 20" + }, + { + "key": "sort", + "value": "name@asc", + "description": "code,name,createdAt" + }, + { + "key": "search", + "value": "", + "description": "code,name" + }, + { + "key": "isActive", + "value": "true,false", + "description": "true,false" + } + ] + } + }, + "response": [ + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/list?perPage=10&page=1&sort=name@asc&search=&isActive=true,false", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "list" + ], + "query": [ + { + "key": "perPage", + "value": "10" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "sort", + "value": "name@asc" + }, + { + "key": "search", + "value": "" + }, + { + "key": "isActive", + "value": "true,false" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Permission list succeed\",\n \"totalData\": 12,\n \"totalPage\": 2,\n \"currentPage\": 1,\n \"perPage\": 10,\n \"availableSort\": [\n \"code\",\n \"name\",\n \"createdAt\"\n ],\n \"availableSearch\": [\n \"code\",\n \"name\"\n ],\n \"data\": [\n {\n \"_id\": \"6298cb21094ec30dfa5d08bb\",\n \"code\": \"PERMISSION_READ\",\n \"name\": \"permission read\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T14:37:21.238Z\"\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08bc\",\n \"code\": \"PERMISSION_UPDATE\",\n \"name\": \"permission update\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T14:37:21.238Z\"\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08b7\",\n \"code\": \"ROLE_CREATE\",\n \"name\": \"role create\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T14:37:21.238Z\"\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08ba\",\n \"code\": \"ROLE_DELETE\",\n \"name\": \"role delete\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T14:37:21.238Z\"\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08b9\",\n \"code\": \"ROLE_READ\",\n \"name\": \"role read\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T14:37:21.238Z\"\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08b8\",\n \"code\": \"ROLE_UPDATE\",\n \"name\": \"role update\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T14:37:21.238Z\"\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08bd\",\n \"code\": \"SETTING_READ\",\n \"name\": \"setting read\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T14:37:21.238Z\"\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08be\",\n \"code\": \"SETTING_UPDATE\",\n \"name\": \"setting update\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T14:37:21.238Z\"\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08b3\",\n \"code\": \"USER_CREATE\",\n \"name\": \"user create\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T14:37:21.237Z\"\n },\n {\n \"_id\": \"6298cb21094ec30dfa5d08b6\",\n \"code\": \"USER_DELETE\",\n \"name\": \"user delete\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T14:37:21.237Z\"\n }\n ]\n}" + } + ] + }, + { + "name": "get", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/get/:permission", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "get", + ":permission" + ], + "variable": [ + { + "key": "permission", + "value": null + } + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/get/:permission", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "get", + ":permission" + ], + "variable": [ + { + "key": "permission", + "value": null + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5200,\n \"message\": \"Permission not found\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/get/:permission", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "get", + ":permission" + ], + "variable": [ + { + "key": "permission", + "value": null + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Permission get succeed\",\n \"data\": {\n \"_id\": \"6298cb21094ec30dfa5d08b6\",\n \"code\": \"USER_DELETE\",\n \"name\": \"user delete\",\n \"isActive\": true,\n \"createdAt\": \"2022-06-02T14:37:21.237Z\"\n }\n}" + } + ] + }, + { + "name": "update", + "request": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"permission test\",\n \"description\": \"test desc\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/update/:permission", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "update", + ":permission" + ], + "variable": [ + { + "key": "permission", + "value": null + } + ] + } + }, + "response": [ + { + "name": "error request", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"permission test\",\n \"description\": \"test desc\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/update/:permission", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "update", + ":permission" + ], + "variable": [ + { + "key": "permission", + "value": null + } + ] + } + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5981,\n \"message\": \"Unprocessable Entity\",\n \"errors\": [\n {\n \"property\": \"name\",\n \"message\": \"name should be a type of string.\"\n }\n ]\n}" + }, + { + "name": "not found", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"permission test\",\n \"description\": \"test desc\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/update/:permission", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "update", + ":permission" + ], + "variable": [ + { + "key": "permission", + "value": null + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5200,\n \"message\": \"Permission not found.\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"permission test\",\n \"description\": \"test desc\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/update/:permission", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "update", + ":permission" + ], + "variable": [ + { + "key": "permission", + "value": null + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Permission update succeed\",\n \"data\": {\n \"_id\": \"6298cb21094ec30dfa5d08ba\"\n }\n}" + } + ] + }, + { + "name": "inactive", + "request": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/update/:permission/inactive", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "update", + ":permission", + "inactive" + ], + "variable": [ + { + "key": "permission", + "value": "6298cb21094ec30dfa5d08b6" + } + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/update/:permission/inactive", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "update", + ":permission", + "inactive" + ], + "variable": [ + { + "key": "permission", + "value": null + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5200,\n \"message\": \"Permission not found\"\n}" + }, + { + "name": "already inactive", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/update/:permission/inactive", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "update", + ":permission", + "inactive" + ], + "variable": [ + { + "key": "permission", + "value": null + } + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5203,\n \"message\": \"Permission active status invalid\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/update/:permission/inactive", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "update", + ":permission", + "inactive" + ], + "variable": [ + { + "key": "permission", + "value": null + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Permission inactive succeed\"\n}" + } + ] + }, + { + "name": "active", + "request": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/update/:permission/active", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "update", + ":permission", + "active" + ], + "variable": [ + { + "key": "permission", + "value": "6298cb21094ec30dfa5d08b6" + } + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/update/:permission/active", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "update", + ":permission", + "active" + ], + "variable": [ + { + "key": "permission", + "value": null + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5200,\n \"message\": \"Permission not found\"\n}" + }, + { + "name": "already active", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/update/:permission/active", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "update", + ":permission", + "active" + ], + "variable": [ + { + "key": "permission", + "value": null + } + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5203,\n \"message\": \"Permission active status invalid\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/permission/update/:permission/active", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "permission", + "update", + ":permission", + "active" + ], + "variable": [ + { + "key": "permission", + "value": null + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Permission active succeed\"\n}" + } + ] + } + ] + }, + { + "name": "setting", + "item": [ + { + "name": "list", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/setting/list?perPage=10&page=1&sort=name@asc&search=", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "setting", + "list" + ], + "query": [ + { + "key": "perPage", + "value": "10", + "description": "max 100" + }, + { + "key": "page", + "value": "1", + "description": "max 20" + }, + { + "key": "sort", + "value": "name@asc", + "description": "name,createdAt" + }, + { + "key": "search", + "value": "", + "description": "name" + } + ] + } + }, + "response": [ + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/setting/list?perPage=10&page=1&sort=name@asc&search=", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "setting", + "list" + ], + "query": [ + { + "key": "perPage", + "value": "10" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "sort", + "value": "name@asc" + }, + { + "key": "search", + "value": "" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"setting.list\",\n \"totalData\": 1,\n \"totalPage\": 1,\n \"currentPage\": 1,\n \"perPage\": 10,\n \"availableSort\": [\n \"name\",\n \"createdAt\"\n ],\n \"availableSearch\": [\n \"name\"\n ],\n \"data\": [\n {\n \"_id\": \"6298cb10e70e751cc2c720c0\",\n \"name\": \"maintenance\",\n \"description\": \"Maintenance Mode\",\n \"value\": false,\n \"createdAt\": \"2022-06-02T14:37:04.149Z\",\n \"updatedAt\": \"2022-06-02T14:37:04.149Z\"\n }\n ]\n}" + } + ] + }, + { + "name": "get", + "request": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/setting/get/:_id", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "setting", + "get", + ":_id" + ], + "variable": [ + { + "key": "_id", + "value": "6274c434f9c679e3ca068ca3" + } + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/setting/get/:_id", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "setting", + "get", + ":_id" + ], + "variable": [ + { + "key": "_id", + "value": "6274c434f9c679e3ca068ca3" + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5900,\n \"message\": \"setting.error.notFound\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/setting/get/:_id", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "setting", + "get", + ":_id" + ], + "variable": [ + { + "key": "_id", + "value": "6274c434f9c679e3ca068ca3" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"setting.get\",\n \"data\": {\n \"_id\": \"6298cb10e70e751cc2c720c0\",\n \"name\": \"maintenance\",\n \"description\": \"Maintenance Mode\",\n \"value\": false,\n \"createdAt\": \"2022-06-02T14:37:04.149Z\",\n \"updatedAt\": \"2022-06-02T14:37:04.149Z\"\n }\n}" + } + ] + }, + { + "name": "update", + "request": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"description\": \"Maintenance Mode Change\",\n \"value\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/setting/update/:_id", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "setting", + "update", + ":_id" + ], + "variable": [ + { + "key": "_id", + "value": "6298cb10e70e751cc2c720c0" + } + ] + } + }, + "response": [ + { + "name": "error request", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"description\": [],\n \"value\": \"false\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/setting/update/:_id", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "setting", + "update", + ":_id" + ], + "variable": [ + { + "key": "_id", + "value": "6274c434f9c679e3ca068ca3" + } + ] + } + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5981,\n \"message\": \"Unprocessable Entity\",\n \"errors\": [\n {\n \"property\": \"description\",\n \"message\": \"description should be a type of string.\"\n }\n ]\n}" + }, + { + "name": "not found", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"description\": \"Maintenance Mode Change\",\n \"value\": true\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/setting/update/:_id", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "setting", + "update", + ":_id" + ], + "variable": [ + { + "key": "_id", + "value": "6274c434f9c679e3ca068ca3" + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5900,\n \"message\": \"setting.error.notFound\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"description\": \"Maintenance Mode Change\",\n \"value\": true\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/admin/setting/update/:_id", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "admin", + "setting", + "update", + ":_id" + ], + "variable": [ + { + "key": "_id", + "value": "6274c434f9c679e3ca068ca3" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"setting.update\",\n \"data\": {\n \"_id\": \"6298cb10e70e751cc2c720c0\"\n }\n}" + } + ] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{AccessToken}}", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + }, + { + "name": "refresh", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{RefreshToken}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/refresh", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "refresh" + ] + } + }, + "response": [ + { + "name": "not found", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{RefreshToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/refresh", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "refresh" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5400,\n \"message\": \"User not found.\"\n}" + }, + { + "name": "user inactive", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{RefreshToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/refresh", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "refresh" + ] + } + }, + "status": "Forbidden", + "code": 403, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5402,\n \"message\": \"User inactive\"\n}" + }, + { + "name": "role inactive", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{RefreshToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/refresh", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "refresh" + ] + } + }, + "status": "Forbidden", + "code": 403, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5500,\n \"message\": \"Role inactive\"\n}" + }, + { + "name": "password expired", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{RefreshToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/refresh", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "refresh" + ] + } + }, + "status": "Forbidden", + "code": 403, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5110,\n \"message\": \"Password expired, you need to change your password asap\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{RefreshToken}}", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/refresh", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "refresh" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Refresh token success\",\n \"data\": {\n \"accessToken\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2Mjk4Y2IzMmJkNTMwYzljODQyNjZjNDAiLCJtb2JpbGVOdW1iZXIiOiIwODExMTExMTExMSIsImVtYWlsIjoiYWRtaW5AbWFpbC5jb20iLCJyb2xlIjp7Im5hbWUiOiJhZG1pbiIsInBlcm1pc3Npb25zIjpbeyJjb2RlIjoiUEVSTUlTU0lPTl9SRUFEIiwiaXNBY3RpdmUiOnRydWV9LHsiY29kZSI6IlJPTEVfQ1JFQVRFIiwiaXNBY3RpdmUiOnRydWV9LHsiY29kZSI6IlJPTEVfREVMRVRFIiwiaXNBY3RpdmUiOnRydWV9LHsiY29kZSI6IlJPTEVfUkVBRCIsImlzQWN0aXZlIjp0cnVlfSx7ImNvZGUiOiJST0xFX1VQREFURSIsImlzQWN0aXZlIjp0cnVlfSx7ImNvZGUiOiJTRVRUSU5HX1JFQUQiLCJpc0FjdGl2ZSI6dHJ1ZX0seyJjb2RlIjoiU0VUVElOR19VUERBVEUiLCJpc0FjdGl2ZSI6dHJ1ZX0seyJjb2RlIjoiVVNFUl9DUkVBVEUiLCJpc0FjdGl2ZSI6dHJ1ZX0seyJjb2RlIjoiVVNFUl9ERUxFVEUiLCJpc0FjdGl2ZSI6dHJ1ZX0seyJjb2RlIjoiVVNFUl9SRUFEIiwiaXNBY3RpdmUiOnRydWV9LHsiY29kZSI6IlVTRVJfVVBEQVRFIiwiaXNBY3RpdmUiOnRydWV9XSwiaXNBY3RpdmUiOnRydWUsImlzQWRtaW4iOnRydWV9LCJwYXNzd29yZEV4cGlyZWQiOiIyMDIyLTEyLTAxVDE0OjM3OjM4LjcwNVoiLCJpc0FjdGl2ZSI6dHJ1ZSwicmVtZW1iZXJNZSI6dHJ1ZSwibG9naW5EYXRlIjoiMjAyMi0wNi0wMlQxNTowMDo1MS4xMTFaIiwiaWF0IjoxNjU0MTgyMDUyLCJuYmYiOjE2NTQxODIwNTIsImV4cCI6MTY1NDE4Mzg1Mn0.EtLN1YxrQ3s3gwj_LGrSbyjrlfLsza7nCnS4th8Lx44\",\n \"refreshToken\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2Mjk4Y2IzMmJkNTMwYzljODQyNjZjNDAiLCJyZW1lbWJlck1lIjp0cnVlLCJsb2dpbkRhdGUiOiIyMDIyLTA2LTAyVDE1OjAwOjUxLjExMVoiLCJpYXQiOjE2NTQxODIwNTEsIm5iZiI6MTY1NDE4MjA1MSwiZXhwIjoxNjU2Nzc0MDUxfQ.u3M7RagBidR5j6woZnOiLUOGG2VVN9aXGv91eYol7qE\"\n }\n}" + } + ] + }, + { + "name": "change password", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{AccessToken}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"oldPassword\": \"{{Password}}\",\n \"newPassword\": \"aaAA@@13333\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/change-password", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "change-password" + ] + } + }, + "response": [ + { + "name": "error request", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"oldPassword\": \"{{Password}}\",\n \"newPassword\": \"aaAA@@13333\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/change-password", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "change-password" + ] + } + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5981,\n \"message\": \"Unprocessable Entity\",\n \"errors\": [\n {\n \"property\": \"newPassword\",\n \"message\": \"en.request.IsPasswordStrong\"\n }\n ]\n}" + }, + { + "name": "not found", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"oldPassword\": \"{{Password}}\",\r\n \"newPassword\": \"aaAA@@13333\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/change-password", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "change-password" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5400,\n \"message\": \"User not found.\"\n}" + }, + { + "name": "password not match", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"oldPassword\": \"{{Password}}\",\r\n \"newPassword\": \"aaAA@@13333\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/change-password", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "change-password" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5108,\n \"message\": \"Password not match\"\n}" + }, + { + "name": "new password must difference", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"oldPassword\": \"{{Password}}\",\n \"newPassword\": \"aaAA@@13333\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/change-password", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "change-password" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5109,\n \"message\": \"Your new password can't be same with previous password\"\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"oldPassword\": \"{{Password}}\",\n \"newPassword\": \"aaAA@@13333\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/change-password", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "change-password" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 200,\n \"message\": \"Change Password Success\"\n}" + } + ] + }, + { + "name": "login", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "// Set Function", + "const cryptoJs = require('crypto-js');", + "const { AES, enc, mode, pad } = cryptoJs;", + "const setUtils = {", + " getCurrentTimestamp: function() {", + " return Date.now();", + " },", + " createXApiKey: function (data, key, iv){", + " const cIv = enc.Utf8.parse(iv);", + " const cipher = AES.encrypt(", + " JSON.stringify(data), ", + " key, ", + " {", + " mode: mode.CBC,", + " padding: pad.Pkcs7,", + " iv: cIv,", + " }", + " );", + "", + " return cipher.toString();", + " }", + "}", + "pm.globals.set('utils', setUtils);", + "let utils = eval(pm.globals.get(\"utils\"));", + "", + "// Set x-timestamp", + "pm.environment.set(\"xTimestamp\", utils.getCurrentTimestamp());", + "", + "// Set x-api-key", + "const xTimestamp = pm.environment.get(\"xTimestamp\");", + "const apiKey = pm.environment.get(\"ApiKey\");", + "const apiHash = pm.environment.get(\"ApiHash\");", + "const apiEncryptionKey = pm.environment.get(\"ApiEncryptionKey\");", + "const apiPassphrase = pm.environment.get(\"ApiPassphrase\");", + "const apiPayload = {", + " \"key\": apiKey,", + " \"timestamp\": parseInt(xTimestamp),", + " \"hash\": apiHash", + "}", + "const apiEncrypted = utils.createXApiKey(apiPayload, apiEncryptionKey, apiPassphrase);", + "const xApiKey = `${apiKey}:${apiEncrypted}`;", + "pm.environment.set(\"ApiEncrypted\", apiEncrypted);", + "pm.environment.set(\"xApiKey\", xApiKey);" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "const responseJson = pm.response.json();", + "const responseStatus = pm.response.code;", + "", + "if(responseStatus === 200){", + " pm.environment.set(\"AccessToken\", responseJson.data.accessToken);", + " pm.environment.set(\"RefreshToken\", responseJson.data.refreshToken);", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{Email}}\",\r\n \"password\": \"{{Password}}\",\r\n \"rememberMe\": true\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/login", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [ + { + "name": "error request", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{Email}}\",\r\n \"password\": \"{{Password}}\",\r\n \"rememberMe\": true\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/login", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5981,\n \"message\": \"Unprocessable Entity\",\n \"errors\": [\n {\n \"property\": \"rememberMe\",\n \"message\": \"rememberMe should be a boolean\"\n }\n ]\n}" + }, + { + "name": "not found", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{Email}}\",\r\n \"password\": \"{{Password}}\",\r\n \"rememberMe\": true\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/login", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5400,\n \"message\": \"User not found.\"\n}" + }, + { + "name": "password not match", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{Email}}\",\r\n \"password\": \"{{Password}}\",\r\n \"rememberMe\": true\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/login", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5108,\n \"message\": \"Password not match\"\n}" + }, + { + "name": "user inactive", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{Email}}\",\r\n \"password\": \"{{Password}}\",\r\n \"rememberMe\": true\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/login", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "status": "Forbidden", + "code": 403, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5402,\n \"message\": \"User inactive\"\n}" + }, + { + "name": "role inactive", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{Email}}\",\r\n \"password\": \"{{Password}}\",\r\n \"rememberMe\": true\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/login", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "status": "Forbidden", + "code": 403, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5500,\n \"message\": \"Role inactive\"\n}" + }, + { + "name": "password expired", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{Email}}\",\r\n \"password\": \"{{Password}}\",\r\n \"rememberMe\": true\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/login", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 5110,\n \"message\": \"Password expired, you need to change your password asap\",\n \"data\": {\n \"accessToken\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWRkYTU1NWM4ZDgwM2QxNGJkZjNkNzciLCJpc0FjdGl2ZSI6dHJ1ZSwicGFzc3dvcmRFeHBpcmVkIjoiMjAyMS0wMS0xMlQwNDo1OTo1NS44ODlaIiwicm9sZSI6eyJuYW1lIjoidXNlciIsInBlcm1pc3Npb25zIjpbXSwiaXNBY3RpdmUiOnRydWUsImlzQWRtaW4iOmZhbHNlfSwiZW1haWwiOiJhbmRyZWNrQG1haWwuY29tIiwibW9iaWxlTnVtYmVyIjoiNjI4MTIzMTIzMzExIiwibG9naW5EYXRlIjoiMjAyMi0wMS0xNFQxNzoyNDoxMy4zMThaIiwicmVtZW1iZXJNZSI6dHJ1ZSwibG9naW5FeHBpcmVkIjoiMjAyMi0wMS0xNFQxNzoyNDoxMy4zMThaIiwiaWF0IjoxNjQyMTgxMDUzLCJuYmYiOjE2NDIxODEwNTMsImV4cCI6MTY0MjI2NzQ1M30.e00m_7KMXF3V3pEodCEXgocS8kCH3dP0KNgmqED2riM\",\n \"refreshToken\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWRkYTU1NWM4ZDgwM2QxNGJkZjNkNzciLCJpc0FjdGl2ZSI6dHJ1ZSwicGFzc3dvcmRFeHBpcmVkIjoiMjAyMS0wMS0xMlQwNDo1OTo1NS44ODlaIiwicm9sZSI6eyJuYW1lIjoidXNlciIsInBlcm1pc3Npb25zIjpbXSwiaXNBY3RpdmUiOnRydWUsImlzQWRtaW4iOmZhbHNlfSwiZW1haWwiOiJhbmRyZWNrQG1haWwuY29tIiwibW9iaWxlTnVtYmVyIjoiNjI4MTIzMTIzMzExIiwibG9naW5EYXRlIjoiMjAyMi0wMS0xNFQxNzoyNDoxMy4zMThaIiwicmVtZW1iZXJNZSI6dHJ1ZSwibG9naW5FeHBpcmVkIjoiMjAyMi0wMS0xNFQxNzoyNDoxMy4zMThaIiwiaWF0IjoxNjQyMTgxMDUzLCJuYmYiOjE2NDIyNjc0NTMsImV4cCI6MTY0MjM1Mzg1M30.xRlpcb6AfRpLmK-yc5z2HnRdhcdYFVGJShEVx08wL_s\"\n }\n}" + }, + { + "name": "success", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-timestamp", + "value": "{{xTimestamp}}", + "type": "text" + }, + { + "key": "x-api-key", + "value": "{{xApiKey}}", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{Email}}\",\r\n \"password\": \"{{Password}}\",\r\n \"rememberMe\": true\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}/auth/login", + "host": [ + "{{BaseUrl}}{{PrefixUrl}}{{VersionUrl}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"statusCode\": 1001,\n \"message\": \"Login success.\",\n \"data\": {\n \"accessToken\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWRkYTU1NWM4ZDgwM2QxNGJkZjNkNzciLCJpc0FjdGl2ZSI6dHJ1ZSwicGFzc3dvcmRFeHBpcmVkIjoiMjAyMy0wMS0xMlQwNDo1OTo1NS44ODlaIiwicm9sZSI6eyJuYW1lIjoidXNlciIsInBlcm1pc3Npb25zIjpbXSwiaXNBY3RpdmUiOnRydWUsImlzQWRtaW4iOmZhbHNlfSwiZW1haWwiOiJhbmRyZWNrQG1haWwuY29tIiwibW9iaWxlTnVtYmVyIjoiNjI4MTIzMTIzMzExIiwibG9naW5EYXRlIjoiMjAyMi0wMS0xNFQwOTo0MDo1OS41MjJaIiwicmVtZW1iZXJNZSI6dHJ1ZSwibG9naW5FeHBpcmVkIjoiMjAyMi0wMS0xNFQwOTo0MDo1OS41MjJaIiwiaWF0IjoxNjQyMTUzMjU5LCJuYmYiOjE2NDIxNTMyNTksImV4cCI6MTY0MjIzOTY1OX0.2S-7yHsZTO-nUjE6E2sAe8HbQEOIO1Xj7qaFPaKXXII\",\n \"refreshToken\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWRkYTU1NWM4ZDgwM2QxNGJkZjNkNzciLCJpc0FjdGl2ZSI6dHJ1ZSwicGFzc3dvcmRFeHBpcmVkIjoiMjAyMy0wMS0xMlQwNDo1OTo1NS44ODlaIiwicm9sZSI6eyJuYW1lIjoidXNlciIsInBlcm1pc3Npb25zIjpbXSwiaXNBY3RpdmUiOnRydWUsImlzQWRtaW4iOmZhbHNlfSwiZW1haWwiOiJhbmRyZWNrQG1haWwuY29tIiwibW9iaWxlTnVtYmVyIjoiNjI4MTIzMTIzMzExIiwibG9naW5EYXRlIjoiMjAyMi0wMS0xNFQwOTo0MDo1OS41MjJaIiwicmVtZW1iZXJNZSI6dHJ1ZSwibG9naW5FeHBpcmVkIjoiMjAyMi0wMS0xNFQwOTo0MDo1OS41MjJaIiwiaWF0IjoxNjQyMTUzMjU5LCJuYmYiOjE2NDIyMzk2NTksImV4cCI6MTY0MjMyNjA1OX0.SkryEIvlJqzvPJaOW3MJVxR_X9Vv3Bp0in8NKoVD_4Y\"\n }\n}" + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/auth-api/endpoints/env.json b/packages/auth-api/endpoints/env.json new file mode 100644 index 0000000..1c8add9 --- /dev/null +++ b/packages/auth-api/endpoints/env.json @@ -0,0 +1,93 @@ +{ + "id": "f8521cf7-6d25-44a0-b69b-a605ba5f217e", + "name": "ac.k", + "values": [ + { + "key": "AccessToken", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2Mjk4Y2IzMmJkNTMwYzljODQyNjZjNDAiLCJtb2JpbGVOdW1iZXIiOiIwODExMTExMTExMSIsImVtYWlsIjoiYWRtaW5AbWFpbC5jb20iLCJyb2xlIjp7Im5hbWUiOiJhZG1pbiIsInBlcm1pc3Npb25zIjpbeyJjb2RlIjoiUEVSTUlTU0lPTl9SRUFEIiwiaXNBY3RpdmUiOnRydWV9LHsiY29kZSI6IlJPTEVfQ1JFQVRFIiwiaXNBY3RpdmUiOnRydWV9LHsiY29kZSI6IlJPTEVfREVMRVRFIiwiaXNBY3RpdmUiOnRydWV9LHsiY29kZSI6IlJPTEVfUkVBRCIsImlzQWN0aXZlIjp0cnVlfSx7ImNvZGUiOiJST0xFX1VQREFURSIsImlzQWN0aXZlIjp0cnVlfSx7ImNvZGUiOiJTRVRUSU5HX1JFQUQiLCJpc0FjdGl2ZSI6dHJ1ZX0seyJjb2RlIjoiU0VUVElOR19VUERBVEUiLCJpc0FjdGl2ZSI6dHJ1ZX0seyJjb2RlIjoiVVNFUl9DUkVBVEUiLCJpc0FjdGl2ZSI6dHJ1ZX0seyJjb2RlIjoiVVNFUl9ERUxFVEUiLCJpc0FjdGl2ZSI6dHJ1ZX0seyJjb2RlIjoiVVNFUl9SRUFEIiwiaXNBY3RpdmUiOnRydWV9LHsiY29kZSI6IlVTRVJfVVBEQVRFIiwiaXNBY3RpdmUiOnRydWV9XSwiaXNBY3RpdmUiOnRydWUsImlzQWRtaW4iOnRydWV9LCJwYXNzd29yZEV4cGlyZWQiOiIyMDIyLTEyLTAxVDE0OjM3OjM4LjcwNVoiLCJpc0FjdGl2ZSI6dHJ1ZSwicmVtZW1iZXJNZSI6dHJ1ZSwibG9naW5EYXRlIjoiMjAyMi0wNi0wMlQxNjoyMToyNS4wNzRaIiwiaWF0IjoxNjU0MTg2ODg1LCJuYmYiOjE2NTQxODY4ODUsImV4cCI6MTY1NDE4ODY4NX0.xxphJIfi6LJGyi_D6Hq-L4t08EUPMKD6uMogHn8NIG0", + "type": "default", + "enabled": true + }, + { + "key": "RefreshToken", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2Mjk4Y2IzMmJkNTMwYzljODQyNjZjNDAiLCJyZW1lbWJlck1lIjp0cnVlLCJsb2dpbkRhdGUiOiIyMDIyLTA2LTAyVDE2OjIxOjI1LjA3NFoiLCJpYXQiOjE2NTQxODY4ODUsIm5iZiI6MTY1NDE4ODY4NSwiZXhwIjoxNjU2Nzc4ODg1fQ.eybMbbn7eUG1roHwz791yuPSG-ZCzGmHxOXiPRMcP0U", + "type": "default", + "enabled": true + }, + { + "key": "Email", + "value": "admin@mail.com", + "type": "default", + "enabled": true + }, + { + "key": "Password", + "value": "aaAA@@123444", + "type": "default", + "enabled": true + }, + { + "key": "VersionUrl", + "value": "/v1", + "type": "default", + "enabled": true + }, + { + "key": "PrefixUrl", + "value": "/api", + "type": "default", + "enabled": true + }, + { + "key": "BaseUrl", + "value": "localhost:3000", + "type": "default", + "enabled": true + }, + { + "key": "ApiKey", + "value": "qwertyuiop12345zxcvbnmkjh", + "type": "default", + "enabled": true + }, + { + "key": "ApiHash", + "value": "e11a023bc0ccf713cb50de9baa5140e59d3d4c52ec8952d9ca60326e040eda54", + "type": "default", + "enabled": true + }, + { + "key": "ApiPassphrase", + "value": "cuwakimacojulawu", + "type": "default", + "enabled": true + }, + { + "key": "ApiEncryptionKey", + "value": "opbUwdiS1FBsrDUoPgZdx", + "type": "default", + "enabled": true + }, + { + "key": "ApiEncrypted", + "value": "U2FsdGVkX1+wqEKWLu1JW6PbmhXJmCDdqdX6zvfv/U2VPSo1mxMSkSLQE4njH+rTrxyqk+aGu7mo5btPs3FIRZ+yXAia2drS4GAnE00+FHqYxVS02+kyjrFYrDTyGKAuQZ/X7wuWJg2IWXlelDrP50Kvoy4BcMA4SdnHvMaZX1Erps4ELZ/i7GauApETxQGV6aZgxUsEmIowHDWvQmVyvQ==", + "type": "default", + "enabled": true + }, + { + "key": "xApiKey", + "value": "qwertyuiop12345zxcvbnmkjh:U2FsdGVkX1/NsFV/wfHncKBib6chJ7JGhfJyt9aSv/ltg0s/P/NaMtyNM7tSiB78clryjyNDUxlNwLeS/O5AlSXoL3x+rVxpetSQJYyjoiA9miQ99RKT7VaRhJBUFDXk+DfeyvsUNpUr6jw0sMZwyANc429j3cRp+Ow7QsLp2Uq33GxJJ2MYzd1hFrg7sfDDRGHLiZaR3V2eXryzqj0GSA==", + "type": "default", + "enabled": true + }, + { + "key": "xTimestamp", + "value": "1654351563156", + "type": "default", + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2022-06-04T14:13:41.466Z", + "_postman_exported_using": "Postman/9.20.3" +} \ No newline at end of file diff --git a/packages/auth-api/integration/aws/aws.s3.constant.ts b/packages/auth-api/integration/aws/aws.s3.constant.ts new file mode 100644 index 0000000..a1c803a --- /dev/null +++ b/packages/auth-api/integration/aws/aws.s3.constant.ts @@ -0,0 +1 @@ +export const INTEGRATION_AWS_URL = '/health/aws'; diff --git a/packages/auth-api/integration/aws/aws.s3.integration.spec.ts b/packages/auth-api/integration/aws/aws.s3.integration.spec.ts new file mode 100644 index 0000000..501b878 --- /dev/null +++ b/packages/auth-api/integration/aws/aws.s3.integration.spec.ts @@ -0,0 +1,63 @@ +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { HealthCommonController } from 'src/health/controller/health.common.controller'; +import { HealthModule } from 'src/health/health.module'; +import { INTEGRATION_AWS_URL } from './aws.s3.constant'; +import request from 'supertest'; +import { faker } from '@faker-js/faker'; +import { TerminusModule } from '@nestjs/terminus'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; + +describe('Aws S3 Integration', () => { + let app: INestApplication; + let helperDateService: HelperDateService; + let authApiService: AuthApiService; + + const apiKey = 'qwertyuiop12345zxcvbnmkjh'; + let xApiKey: string; + let timestamp: number; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule, HealthModule, TerminusModule], + controllers: [HealthCommonController], + }).compile(); + + app = moduleRef.createNestApplication(); + helperDateService = app.get(HelperDateService); + authApiService = app.get(AuthApiService); + + timestamp = helperDateService.timestamp(); + const apiEncryption = await authApiService.encryptApiKey( + { + key: apiKey, + timestamp, + hash: 'e11a023bc0ccf713cb50de9baa5140e59d3d4c52ec8952d9ca60326e040eda54', + }, + 'opbUwdiS1FBsrDUoPgZdx', + 'cuwakimacojulawu' + ); + xApiKey = `${apiKey}:${apiEncryption}`; + + await app.init(); + }); + + it(`GET ${INTEGRATION_AWS_URL} Success`, async () => { + const response = await request(app.getHttpServer()) + .get(INTEGRATION_AWS_URL) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + afterAll(async () => { + await app.close(); + }); +}); diff --git a/packages/auth-api/integration/database/database.constant.ts b/packages/auth-api/integration/database/database.constant.ts new file mode 100644 index 0000000..cb35f64 --- /dev/null +++ b/packages/auth-api/integration/database/database.constant.ts @@ -0,0 +1 @@ +export const INTEGRATION_DATABASE_URL = '/health/database'; diff --git a/packages/auth-api/integration/database/database.integration.spec.ts b/packages/auth-api/integration/database/database.integration.spec.ts new file mode 100644 index 0000000..bbb4c1e --- /dev/null +++ b/packages/auth-api/integration/database/database.integration.spec.ts @@ -0,0 +1,63 @@ +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { TerminusModule } from '@nestjs/terminus'; +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { HealthCommonController } from 'src/health/controller/health.common.controller'; +import { HealthModule } from 'src/health/health.module'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import request from 'supertest'; +import { faker } from '@faker-js/faker'; +import { INTEGRATION_DATABASE_URL } from './database.constant'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; + +describe('Database Integration', () => { + let app: INestApplication; + let helperDateService: HelperDateService; + let authApiService: AuthApiService; + + const apiKey = 'qwertyuiop12345zxcvbnmkjh'; + let xApiKey: string; + let timestamp: number; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule, HealthModule, TerminusModule], + controllers: [HealthCommonController], + }).compile(); + + app = moduleRef.createNestApplication(); + helperDateService = app.get(HelperDateService); + authApiService = app.get(AuthApiService); + + timestamp = helperDateService.timestamp(); + const apiEncryption = await authApiService.encryptApiKey( + { + key: apiKey, + timestamp, + hash: 'e11a023bc0ccf713cb50de9baa5140e59d3d4c52ec8952d9ca60326e040eda54', + }, + 'opbUwdiS1FBsrDUoPgZdx', + 'cuwakimacojulawu' + ); + xApiKey = `${apiKey}:${apiEncryption}`; + + await app.init(); + }); + + it(`GET ${INTEGRATION_DATABASE_URL} Success`, async () => { + const response = await request(app.getHttpServer()) + .get(INTEGRATION_DATABASE_URL) + .set('user-agent', faker.internet.userAgent()) + .set('x-timestamp', timestamp.toString()) + .set('x-api-key', xApiKey); + + expect(response.status).toEqual(HttpStatus.OK); + expect(response.body.statusCode).toEqual(HttpStatus.OK); + + return; + }); + + afterAll(async () => { + await app.close(); + }); +}); diff --git a/packages/auth-api/integration/jest.json b/packages/auth-api/integration/jest.json new file mode 100644 index 0000000..df4bb5c --- /dev/null +++ b/packages/auth-api/integration/jest.json @@ -0,0 +1,32 @@ +{ + "testTimeout": 10000, + "rootDir": "..", + "modulePaths": [ + "." + ], + "testEnvironment": "node", + "testMatch": [ + "/integration/**/*.spec.ts" + ], + "collectCoverage": true, + "coverageDirectory": "integration-coverage", + "collectCoverageFrom": [ + "./integration" + ], + "coverageThreshold": { + "global": { + "branches": 90, + "functions": 90, + "lines": 90, + "statements": 90 + } + }, + "moduleFileExtensions": [ + "js", + "ts", + "json" + ], + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/packages/auth-api/nest-cli.json b/packages/auth-api/nest-cli.json new file mode 100644 index 0000000..d7901aa --- /dev/null +++ b/packages/auth-api/nest-cli.json @@ -0,0 +1,9 @@ +{ + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "assets": [ + { "include": "message/languages/**/*", "watchAssets": true } + ] + } +} diff --git a/packages/auth-api/nodemon.json b/packages/auth-api/nodemon.json new file mode 100644 index 0000000..4eb2ea6 --- /dev/null +++ b/packages/auth-api/nodemon.json @@ -0,0 +1,5 @@ +{ + "watch": ["src"], + "ext": "ts", + "exec": "ts-node src/main" +} \ No newline at end of file diff --git a/packages/auth-api/package.json b/packages/auth-api/package.json new file mode 100644 index 0000000..dd7c0f2 --- /dev/null +++ b/packages/auth-api/package.json @@ -0,0 +1,133 @@ +{ + "name": "@sopenapi/api-auth", + "version": "0.1.0", + "description": "S-OpenAPI for managing Authentications", + "repository": { + "type": "git", + "url": "git+https://github.com/ja88a/openapi-nestjs-auth-mongo.git" + }, + "author": { + "name": "Jabba 01", + "email": "r0g3r@tuta.io" + }, + "private": true, + "license": "MIT", + "scripts": { + "build": "nest build", + "clean": "rm -rf ./dist ./*.tsbuildinfo ./coverage ./integration-coverage ./logs", + "deadcode": "ts-prune --project tsconfig.json", + "format": "prettier --write '{src,test,e2e,integration}/**/*.ts'", + "format:e2e": "prettier --write e2e/**/*.ts", + "format:integration": "prettier --write integration/**/*.ts", + "format:src": "prettier --write src/**/*.ts", + "format:test": "prettier --write test/**/*.ts", + "lint": "eslint --ext .ts,.tsx '{src,test,e2e,integration}/**/*.ts' --no-error-on-unmatched-pattern", + "lint:fix": "eslint --ext .ts,.tsx '{src,test,e2e,integration}/**/*.ts' --fix --no-error-on-unmatched-pattern", + "lint:e2e": "eslint --ext .ts,.tsx e2e/**/*.ts --no-error-on-unmatched-pattern", + "lint:integration": "eslint --ext .ts,.tsx integration/**/*.ts --no-error-on-unmatched-pattern", + "lint:src": "eslint --ext .ts,.tsx src/**/*.ts --no-error-on-unmatched-pattern", + "lint:test": "eslint --ext .ts,.tsx test/**/*.ts --no-error-on-unmatched-pattern", + "migrate": "nestjs-command insert:setting && nestjs-command insert:authapis && nestjs-command insert:permission && nestjs-command insert:role && nestjs-command insert:user", + "prebuild": "rimraf dist", + "prepare": "husky install", + "reset": "yarn clean && rm -rf ./node_modules", + "rollback": "nestjs-command remove:user && nestjs-command remove:role && nestjs-command remove:permission && nestjs-command remove:authapis && nestjs-command remove:setting", + "spell": "cspell lint --config cspell.json {src,test,e2e,integration}/**/*.ts readme.md tsconfig*.json docker* package.json nodemon.json license.md .github/* .husky/* --color --gitignore --no-must-find-files --no-summary --no-progress || true", + "spell:e2e": "cspell lint --config cspell.json e2e/**/*.ts --color --gitignore --no-must-find-files --no-summary --no-progress || true", + "spell:integration": "cspell lint --config cspell.json integration/**/*.ts --color --gitignore --no-must-find-files --no-summary --no-progress || true", + "spell:src": "cspell lint --config cspell.json src/**/*.ts --color --gitignore --no-must-find-files --no-summary --no-progress || true", + "spell:test": "cspell lint --config cspell.json test/**/*.ts --color --gitignore --no-must-find-files --no-summary --no-progress || true", + "start": "nest start", + "start:debug": "nest start --debug --watch", + "start:dev": "nest start --watch", + "start:prod": "node dist/main", + "test": "yarn test:unit && yarn test:integration && yarn test:e2e", + "test:e2e": "jest --config e2e/jest.json --detectOpenHandles --passWithNoTests --forceExit", + "test:integration": "jest --config integration/jest.json --detectOpenHandles --passWithNoTests --forceExit", + "test:unit": "jest --config test/jest.json --detectOpenHandles --passWithNoTests --forceExit" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.113.0", + "@faker-js/faker": "^7.3.0", + "@nestjs/axios": "^0.0.8", + "@nestjs/common": "^8.4.7", + "@nestjs/config": "^2.1.0", + "@nestjs/core": "^8.4.7", + "@nestjs/jwt": "^8.0.1", + "@nestjs/mongoose": "^9.1.1", + "@nestjs/passport": "^8.2.2", + "@nestjs/platform-express": "^8.4.7", + "@nestjs/schedule": "^2.0.1", + "@nestjs/terminus": "^8.0.8", + "@types/response-time": "^2.3.5", + "bcrypt": "^5.0.1", + "cache-manager": "^4.0.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.13.2", + "compression": "^1.7.4", + "crypto-js": "^4.1.1", + "exceljs": "^4.3.0", + "express-rate-limit": "^6.4.0", + "geolib": "^3.3.3", + "helmet": "^5.1.0", + "moment": "^2.29.3", + "moment-timezone": "^0.5.34", + "mongoose": "^6.4.0", + "morgan": "^1.10.0", + "nest-winston": "^1.6.2", + "nestjs-command": "^3.1.1", + "nestjs-i18n": "^9.1.2", + "passport": "^0.6.0", + "passport-headerapikey": "^1.2.2", + "passport-jwt": "^4.0.0", + "reflect-metadata": "^0.1.13", + "response-time": "^2.3.2", + "rimraf": "^3.0.2", + "rotating-file-stream": "^3.0.4", + "rxjs": "^7.5.5", + "ua-parser-js": "^1.0.2", + "winston": "^3.7.2", + "winston-daily-rotate-file": "^4.7.1", + "yargs": "^17.5.1" + }, + "devDependencies": { + "@nestjs/cli": "^8.2.6", + "@nestjs/schematics": "^8.0.11", + "@nestjs/testing": "^8.4.7", + "@types/bcrypt": "^5.0.0", + "@types/bytes": "^3.1.1", + "@types/cache-manager": "^4.0.0", + "@types/compression": "^1.7.2", + "@types/cors": "^2.8.12", + "@types/cron": "^2.0.0", + "@types/crypto-js": "^4.1.1", + "@types/express": "^4.17.13", + "@types/jest": "^28.1.3", + "@types/lodash": "^4.14.182", + "@types/morgan": "^1.9.3", + "@types/ms": "^0.7.31", + "@types/multer": "^1.4.7", + "@types/node": "^18.0.0", + "@types/passport-jwt": "^3.0.6", + "@types/supertest": "^2.0.12", + "@types/ua-parser-js": "^0.7.36", + "@types/uuid": "^8.3.4", + "@types/ws": "^8.5.3", + "@typescript-eslint/eslint-plugin": "^5.29.0", + "@typescript-eslint/parser": "^5.29.0", + "cspell": "^6.1.2", + "eslint": "^8.18.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "husky": "^8.0.1", + "jest": "^28.1.1", + "prettier": "^2.7.1", + "supertest": "^6.2.3", + "ts-jest": "^28.0.5", + "ts-loader": "^9.3.0", + "ts-node": "^10.8.1", + "ts-prune": "^0.10.3", + "tsconfig-paths": "^4.0.0", + "typescript": "^4.7.4" + } +} diff --git a/packages/auth-api/scripts/docker-run.sh b/packages/auth-api/scripts/docker-run.sh new file mode 100755 index 0000000..1cbd04f --- /dev/null +++ b/packages/auth-api/scripts/docker-run.sh @@ -0,0 +1,126 @@ +#!/bin/bash +APPNAME="sopenapi-auth" + +CLEAN="clean" +RUN="start" +RESTART="rebuild" +STOP="stop" +MONGO="mongo" +LOGS="logs" + +if [ "$#" -eq 0 ] || [ $1 = "-h" ] || [ $1 = "--help" ]; then + echo "Usage: ./docker-run.sh [OPTIONS] COMMAND [arg...]" + echo " ./docker-run.sh [ -h | --help ]" + echo "" + echo "Options:" + echo " -h, --help Prints usage." + echo "" + echo "Commands:" + echo " $RUN - Build and Run $APPNAME related containers." + echo " $RESTART - Stop, Clean, force re-Build and Run $APPNAME related containers." + echo " $STOP - Stop $APPNAME related containers." + echo " $CLEAN - Stop and Remove $APPNAME related containers." + echo " $MONGO - Access the MongoDB shell." + echo " $LOGS - Show and follow logs of $APPNAME." + exit +fi + +clean() { + stop_existing + remove_stopped_containers + remove_unused_volumes +} + +run() { + echo "Stopping existing containers..." + stop_existing + + echo "Running Docker..." + docker compose up --build +} + +restart() { + echo "Cleaning up..." + clean + + echo "Force containers rebuild & start" + docker compose up --build --force-recreate --abort-on-container-exit +} + +stop_existing() { + MYAPP="$(docker ps --all --quiet --filter=name=$APPNAME)" + REDIS="$(docker ps --all --quiet --filter=name=redis)" + MONGO="$(docker ps --all --quiet --filter=name=mongodb)" + + if [ -n "$MYAPP" ]; then + docker stop $MYAPP + fi + + if [ -n "$REDIS" ]; then + docker stop $REDIS + fi + + if [ -n "$MONGO" ]; then + docker stop $MONGO + fi +} + +remove_stopped_containers() { + CONTAINERS="$(docker ps -a -f status=exited -q)" + if [ ${#CONTAINERS} -gt 0 ]; then + echo "Removing all stopped containers." + docker rm $CONTAINERS + else + echo "There are no stopped containers to be removed." + fi +} + +remove_unused_volumes() { + CONTAINERS="$(docker volume ls -qf dangling=true)" + if [ ${#CONTAINERS} -gt 0 ]; then + echo "Removing all unused volumes." + docker volume rm $CONTAINERS + else + echo "There are no unused volumes to be removed." + fi +} + +mongo_shell() { + docker exec -it mongodb mongosh +} + +logs_show() { + docker logs $APPNAME --follow +} + +if [ $1 = $CLEAN ]; then + echo "Cleaning..." + clean + exit +fi + +if [ $1 = $RUN ]; then + run + exit +fi + +if [ $1 = $RESTART ]; then + echo "Rebuild & start..." + restart + exit +fi + +if [ $1 = $STOP ]; then + stop_existing + exit +fi + +if [ $1 = $MONGO ]; then + mongo_shell + exit +fi + +if [ $1 = $LOGS ]; then + logs_show() + exit +fi \ No newline at end of file diff --git a/packages/auth-api/src/app/app.module.ts b/packages/auth-api/src/app/app.module.ts new file mode 100644 index 0000000..b5ec708 --- /dev/null +++ b/packages/auth-api/src/app/app.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { CoreModule } from 'src/core/core.module'; +import { TaskModule } from 'src/task/task.module'; +import { AppRouterModule } from './app.router.module'; +@Module({ + controllers: [], + providers: [], + imports: [ + // Core + CoreModule, + + // Task + TaskModule.register(), + + // Router + AppRouterModule.register(), + ], +}) +export class AppModule {} diff --git a/packages/auth-api/src/app/app.router.module.ts b/packages/auth-api/src/app/app.router.module.ts new file mode 100644 index 0000000..3930af6 --- /dev/null +++ b/packages/auth-api/src/app/app.router.module.ts @@ -0,0 +1,64 @@ +import { DynamicModule, Module } from '@nestjs/common'; +import { RouterModule } from '@nestjs/core'; +import { RouterAdminModule } from 'src/router/router.admin.module'; +import { RouterCallbackModule } from 'src/router/router.callback.module'; +import { RouterCommonModule } from 'src/router/router.common.module'; +import { RouterEnumModule } from 'src/router/router.enum.module'; +import { RouterPublicModule } from 'src/router/router.public.module'; +import { RouterTestModule } from 'src/router/router.test.module'; + +@Module({}) +export class AppRouterModule { + static register(): DynamicModule { + if (process.env.APP_HTTP_ON === 'true') { + return { + module: AppRouterModule, + controllers: [], + providers: [], + exports: [], + imports: [ + RouterCommonModule, + RouterTestModule, + RouterEnumModule, + RouterPublicModule, + RouterAdminModule, + RouterCallbackModule, + RouterModule.register([ + { + path: '/', + module: RouterCommonModule, + }, + { + path: '/test', + module: RouterTestModule, + }, + { + path: '/enum', + module: RouterEnumModule, + }, + { + path: '/admin', + module: RouterAdminModule, + }, + { + path: '/public', + module: RouterPublicModule, + }, + { + path: '/callback', + module: RouterCallbackModule, + }, + ]), + ], + }; + } + + return { + module: AppRouterModule, + providers: [], + exports: [], + controllers: [], + imports: [], + }; + } +} diff --git a/packages/auth-api/src/auth/auth.constant.ts b/packages/auth-api/src/auth/auth.constant.ts new file mode 100644 index 0000000..1ed5177 --- /dev/null +++ b/packages/auth-api/src/auth/auth.constant.ts @@ -0,0 +1,28 @@ +export enum ENUM_AUTH_STATUS_CODE_ERROR { + AUTH_GUARD_BASIC_TOKEN_NEEDED_ERROR = 5100, + AUTH_GUARD_BASIC_TOKEN_INVALID_ERROR = 5101, + AUTH_GUARD_JWT_ACCESS_TOKEN_ERROR = 5102, + AUTH_GUARD_JWT_REFRESH_TOKEN_ERROR = 5103, + AUTH_GUARD_INACTIVE_ERROR = 5104, + AUTH_GUARD_ROLE_INACTIVE_ERROR = 5105, + AUTH_GUARD_ADMIN_ERROR = 5106, + AUTH_GUARD_PASSWORD_EXPIRED_ERROR = 5107, + AUTH_PASSWORD_NOT_MATCH_ERROR = 5108, + AUTH_PASSWORD_NEW_MUST_DIFFERENCE_ERROR = 5109, + AUTH_PASSWORD_EXPIRED_ERROR = 5110, + + AUTH_GUARD_API_KEY_NEEDED_ERROR = 5121, + AUTH_GUARD_API_KEY_PREFIX_INVALID_ERROR = 5122, + AUTH_GUARD_API_KEY_SCHEMA_INVALID_ERROR = 5123, + AUTH_GUARD_API_KEY_TIMESTAMP_NOT_MATCH_WITH_REQUEST_ERROR = 5124, + AUTH_GUARD_API_KEY_NOT_FOUND_ERROR = 5125, + AUTH_GUARD_API_KEY_INACTIVE_ERROR = 5126, + AUTH_GUARD_API_KEY_INVALID_ERROR = 5127, +} + +export enum ENUM_AUTH_STATUS_CODE_SUCCESS { + AUTH_LOGIN_SUCCESS = 1001, +} + +export const AUTH_ADMIN_META_KEY = 'AuthAdminMetaKey'; +export const AUTH_EXCLUDE_API_KEY_META_KEY = 'AuthExcludeApiKeyMetaKey'; diff --git a/packages/auth-api/src/auth/auth.decorator.ts b/packages/auth-api/src/auth/auth.decorator.ts new file mode 100644 index 0000000..2d93c3a --- /dev/null +++ b/packages/auth-api/src/auth/auth.decorator.ts @@ -0,0 +1,94 @@ +import { + UseGuards, + createParamDecorator, + ExecutionContext, + applyDecorators, + SetMetadata, +} from '@nestjs/common'; +import { PermissionPayloadDefaultGuard } from 'src/permission/guard/payload/permission.default.guard'; +import { + ENUM_PERMISSIONS, + PERMISSION_META_KEY, +} from 'src/permission/permission.constant'; +import { + AUTH_ADMIN_META_KEY, + AUTH_EXCLUDE_API_KEY_META_KEY, +} from './auth.constant'; +import { BasicGuard } from './guard/basic/auth.basic.guard'; +import { JwtRefreshGuard } from './guard/jwt-refresh/auth.jwt-refresh.guard'; +import { JwtGuard } from './guard/jwt/auth.jwt.guard'; +import { AuthPayloadAdminGuard } from './guard/payload/auth.payload.admin.guard'; +import { AuthPayloadDefaultGuard } from './guard/payload/auth.payload.default.guard'; +import { AuthPayloadPasswordExpiredGuard } from './guard/payload/auth.payload.password-expired.guard'; + +export function AuthJwtGuard(...permissions: ENUM_PERMISSIONS[]): any { + return applyDecorators( + UseGuards( + JwtGuard, + AuthPayloadDefaultGuard, + PermissionPayloadDefaultGuard + ), + SetMetadata(PERMISSION_META_KEY, permissions) + ); +} + +export function AuthPublicJwtGuard(...permissions: ENUM_PERMISSIONS[]): any { + return applyDecorators( + UseGuards( + JwtGuard, + AuthPayloadDefaultGuard, + AuthPayloadPasswordExpiredGuard, + AuthPayloadAdminGuard, + PermissionPayloadDefaultGuard + ), + SetMetadata(PERMISSION_META_KEY, permissions), + SetMetadata(AUTH_ADMIN_META_KEY, [false]) + ); +} + +export function AuthAdminJwtGuard(...permissions: ENUM_PERMISSIONS[]) { + return applyDecorators( + UseGuards( + JwtGuard, + AuthPayloadDefaultGuard, + AuthPayloadPasswordExpiredGuard, + AuthPayloadAdminGuard, + PermissionPayloadDefaultGuard + ), + SetMetadata(PERMISSION_META_KEY, permissions), + SetMetadata(AUTH_ADMIN_META_KEY, [true]) + ); +} + +export function AuthRefreshJwtGuard(): any { + return applyDecorators(UseGuards(JwtRefreshGuard)); +} + +export function AuthBasicGuard(): any { + return applyDecorators(UseGuards(BasicGuard)); +} + +export const AuthExcludeApiKey = () => + SetMetadata(AUTH_EXCLUDE_API_KEY_META_KEY, true); + +export const User = createParamDecorator( + (data: string, ctx: ExecutionContext): Record => { + const { user } = ctx.switchToHttp().getRequest(); + return data ? user[data] : user; + } +); + +export const ApiKey = createParamDecorator( + (data: string, ctx: ExecutionContext): Record => { + const { apiKey } = ctx.switchToHttp().getRequest(); + return data ? apiKey[data] : apiKey; + } +); + +export const Token = createParamDecorator( + (data: string, ctx: ExecutionContext): string => { + const { headers } = ctx.switchToHttp().getRequest(); + const { authorization } = headers; + return authorization ? authorization.split(' ')[1] : undefined; + } +); diff --git a/packages/auth-api/src/auth/auth.interface.ts b/packages/auth-api/src/auth/auth.interface.ts new file mode 100644 index 0000000..e3793a3 --- /dev/null +++ b/packages/auth-api/src/auth/auth.interface.ts @@ -0,0 +1,39 @@ +import { Types } from 'mongoose'; +import { AuthApiCreateDto } from './dto/auth.api.create.dto'; + +export interface IAuthPassword { + salt: string; + passwordHash: string; + passwordExpired: Date; +} + +export interface IAuthPayloadOptions { + loginDate: Date; +} + +export interface IAuthApiPayload { + _id: string; + key: string; + name: string; + description: string; +} + +export interface IAuthApiDocument { + _id: Types.ObjectId; + secret: string; + passphrase: string; + encryptionKey: string; +} + +export interface IAuthApiCreate extends AuthApiCreateDto { + key?: string; + secret?: string; + passphrase?: string; + encryptionKey?: string; +} + +export interface IAuthApiRequestHashedData { + key: string; + timestamp: number; + hash: string; +} diff --git a/packages/auth-api/src/auth/auth.module.ts b/packages/auth-api/src/auth/auth.module.ts new file mode 100644 index 0000000..b61c44f --- /dev/null +++ b/packages/auth-api/src/auth/auth.module.ts @@ -0,0 +1,49 @@ +import { Module } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { APP_GUARD, Reflector } from '@nestjs/core'; +import { MongooseModule } from '@nestjs/mongoose'; +import { JwtStrategy } from 'src/auth/guard/jwt/auth.jwt.strategy'; +import { DATABASE_CONNECTION_NAME } from 'src/database/database.constant'; +import { ApiKeyGuard } from './guard/api-key/auth.api-key.guard'; +import { ApiKeyStrategy } from './guard/api-key/auth.api-key.strategy'; +import { JwtRefreshStrategy } from './guard/jwt-refresh/auth.jwt-refresh.strategy'; +import { + AuthApiDatabaseName, + AuthApiEntity, + AuthApiSchema, +} from './schema/auth.api.schema'; +import { AuthApiBulkService } from './service/auth.api.bulk.service'; +import { AuthApiService } from './service/auth.api.service'; +import { AuthService } from './service/auth.service'; + +@Module({ + providers: [ + AuthService, + AuthApiService, + AuthApiBulkService, + JwtStrategy, + JwtRefreshStrategy, + ApiKeyStrategy, + { + provide: APP_GUARD, + inject: [ConfigService, Reflector], + useFactory: (configService: ConfigService, reflector: Reflector) => + new ApiKeyGuard(configService, reflector), + }, + ], + exports: [AuthService, AuthApiService, AuthApiBulkService], + controllers: [], + imports: [ + MongooseModule.forFeature( + [ + { + name: AuthApiEntity.name, + schema: AuthApiSchema, + collection: AuthApiDatabaseName, + }, + ], + DATABASE_CONNECTION_NAME + ), + ], +}) +export class AuthModule {} diff --git a/packages/auth-api/src/auth/controller/auth.common.controller.ts b/packages/auth-api/src/auth/controller/auth.common.controller.ts new file mode 100644 index 0000000..800147f --- /dev/null +++ b/packages/auth-api/src/auth/controller/auth.common.controller.ts @@ -0,0 +1,260 @@ +import { + Controller, + Post, + Body, + HttpStatus, + HttpCode, + NotFoundException, + BadRequestException, + ForbiddenException, + InternalServerErrorException, + Patch, +} from '@nestjs/common'; +import { ENUM_USER_STATUS_CODE_ERROR } from 'src/user/user.constant'; +import { ENUM_ROLE_STATUS_CODE_ERROR } from 'src/role/role.constant'; +import { UserService } from 'src/user/service/user.service'; +import { AuthService } from '../service/auth.service'; +import { + ENUM_AUTH_STATUS_CODE_ERROR, + ENUM_AUTH_STATUS_CODE_SUCCESS, +} from '../auth.constant'; +import { Response } from 'src/utils/response/response.decorator'; +import { IResponse } from 'src/utils/response/response.interface'; +import { IUserDocument } from 'src/user/user.interface'; +import { ENUM_LOGGER_ACTION } from 'src/logger/logger.constant'; +import { + AuthJwtGuard, + AuthRefreshJwtGuard, + Token, + User, +} from '../auth.decorator'; +import { ENUM_STATUS_CODE_ERROR } from 'src/utils/error/error.constant'; +import { UserDocument } from 'src/user/schema/user.schema'; +import { AuthLoginDto } from '../dto/auth.login.dto'; +import { AuthChangePasswordDto } from '../dto/auth.change-password.dto'; +import { AuthLoginSerialization } from '../serialization/auth.login.serialization'; +import { SuccessException } from 'src/utils/error/exception/error.success.exception'; +import { ErrorMeta } from 'src/utils/error/error.decorator'; +import { Logger } from 'src/logger/logger.decorator'; + +@Controller({ + version: '1', + path: '/auth', +}) +export class AuthCommonController { + constructor( + private readonly userService: UserService, + private readonly authService: AuthService + ) {} + + @Response('auth.login', { + statusCode: ENUM_AUTH_STATUS_CODE_SUCCESS.AUTH_LOGIN_SUCCESS, + }) + @Logger(ENUM_LOGGER_ACTION.LOGIN, { tags: ['login', 'withEmail'] }) + @HttpCode(HttpStatus.OK) + @ErrorMeta(AuthCommonController.name, 'login') + @Post('/login') + async login(@Body() body: AuthLoginDto): Promise { + const rememberMe: boolean = body.rememberMe ? true : false; + const user: IUserDocument = + await this.userService.findOne( + { + email: body.email, + }, + { + populate: { + role: true, + permission: true, + }, + } + ); + + if (!user) { + throw new NotFoundException({ + statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR, + message: 'user.error.notFound', + }); + } + + const validate: boolean = await this.authService.validateUser( + body.password, + user.password + ); + + if (!validate) { + throw new BadRequestException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_PASSWORD_NOT_MATCH_ERROR, + message: 'auth.error.passwordNotMatch', + }); + } else if (!user.isActive) { + throw new ForbiddenException({ + statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_IS_INACTIVE_ERROR, + message: 'user.error.inactive', + }); + } else if (!user.role.isActive) { + throw new ForbiddenException({ + statusCode: ENUM_ROLE_STATUS_CODE_ERROR.ROLE_IS_INACTIVE_ERROR, + message: 'role.error.inactive', + }); + } + + const safe: AuthLoginSerialization = + await this.authService.serializationLogin(user); + + const payloadAccessToken: Record = + await this.authService.createPayloadAccessToken(safe, rememberMe); + const payloadRefreshToken: Record = + await this.authService.createPayloadRefreshToken(safe, rememberMe, { + loginDate: payloadAccessToken.loginDate, + }); + + const accessToken: string = await this.authService.createAccessToken( + payloadAccessToken + ); + + const refreshToken: string = await this.authService.createRefreshToken( + payloadRefreshToken, + rememberMe + ); + + const checkPasswordExpired: boolean = + await this.authService.checkPasswordExpired(user.passwordExpired); + + if (checkPasswordExpired) { + throw new SuccessException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_PASSWORD_EXPIRED_ERROR, + message: 'auth.error.passwordExpired', + data: { + accessToken, + refreshToken, + }, + }); + } + + return { + accessToken, + refreshToken, + }; + } + + @Response('auth.refresh') + @AuthRefreshJwtGuard() + @HttpCode(HttpStatus.OK) + @ErrorMeta(AuthCommonController.name, 'refresh') + @Post('/refresh') + async refresh( + @User() + { _id, rememberMe, loginDate }: Record, + @Token() refreshToken: string + ): Promise { + const user: IUserDocument = + await this.userService.findOneById(_id, { + populate: { + role: true, + permission: true, + }, + }); + + if (!user) { + throw new NotFoundException({ + statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR, + message: 'user.error.notFound', + }); + } else if (!user.isActive) { + throw new ForbiddenException({ + statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_IS_INACTIVE_ERROR, + message: 'user.error.inactive', + }); + } else if (!user.role.isActive) { + throw new ForbiddenException({ + statusCode: ENUM_ROLE_STATUS_CODE_ERROR.ROLE_IS_INACTIVE_ERROR, + message: 'role.error.inactive', + }); + } + + const checkPasswordExpired: boolean = + await this.authService.checkPasswordExpired(user.passwordExpired); + + if (checkPasswordExpired) { + throw new ForbiddenException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_PASSWORD_EXPIRED_ERROR, + message: 'auth.error.passwordExpired', + }); + } + + const safe: AuthLoginSerialization = + await this.authService.serializationLogin(user); + const payloadAccessToken: Record = + await this.authService.createPayloadAccessToken(safe, rememberMe, { + loginDate, + }); + + const accessToken: string = await this.authService.createAccessToken( + payloadAccessToken + ); + + return { + accessToken, + refreshToken, + }; + } + + @Response('auth.changePassword') + @AuthJwtGuard() + @ErrorMeta(AuthCommonController.name, 'changePassword') + @Patch('/change-password') + async changePassword( + @Body() body: AuthChangePasswordDto, + @User('_id') _id: string + ): Promise { + const user: UserDocument = await this.userService.findOneById(_id); + if (!user) { + throw new NotFoundException({ + statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR, + message: 'user.error.notFound', + }); + } + + const matchPassword: boolean = await this.authService.validateUser( + body.oldPassword, + user.password + ); + if (!matchPassword) { + throw new BadRequestException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_PASSWORD_NOT_MATCH_ERROR, + message: 'auth.error.passwordNotMatch', + }); + } + + const newMatchPassword: boolean = await this.authService.validateUser( + body.newPassword, + user.password + ); + if (newMatchPassword) { + throw new BadRequestException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_PASSWORD_NEW_MUST_DIFFERENCE_ERROR, + message: 'auth.error.newPasswordMustDifference', + }); + } + + try { + const password = await this.authService.createPassword( + body.newPassword + ); + + await this.userService.updatePassword(user._id, password); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + + return; + } +} diff --git a/packages/auth-api/src/auth/controller/auth.public.controller.ts b/packages/auth-api/src/auth/controller/auth.public.controller.ts new file mode 100644 index 0000000..0cc573f --- /dev/null +++ b/packages/auth-api/src/auth/controller/auth.public.controller.ts @@ -0,0 +1,129 @@ +import { + BadRequestException, + Body, + Controller, + InternalServerErrorException, + NotFoundException, + Post, +} from '@nestjs/common'; +import { ENUM_ROLE_STATUS_CODE_ERROR } from 'src/role/role.constant'; +import { RoleDocument } from 'src/role/schema/role.schema'; +import { RoleService } from 'src/role/service/role.service'; +import { UserService } from 'src/user/service/user.service'; +import { ENUM_USER_STATUS_CODE_ERROR } from 'src/user/user.constant'; +import { IUserCheckExist, IUserDocument } from 'src/user/user.interface'; +import { ENUM_STATUS_CODE_ERROR } from 'src/utils/error/error.constant'; +import { ErrorMeta } from 'src/utils/error/error.decorator'; +import { Response } from 'src/utils/response/response.decorator'; +import { IResponse } from 'src/utils/response/response.interface'; +import { AuthSignUpDto } from '../dto/auth.sign-up.dto'; +import { AuthLoginSerialization } from '../serialization/auth.login.serialization'; +import { AuthService } from '../service/auth.service'; + +@Controller({ + version: '1', + path: '/auth', +}) +export class AuthPublicController { + constructor( + private readonly userService: UserService, + private readonly authService: AuthService, + private readonly roleService: RoleService + ) {} + + @Response('auth.signUp') + @ErrorMeta(AuthPublicController.name, 'signUp') + @Post('/sign-up') + async signUp( + @Body() + { email, mobileNumber, ...body }: AuthSignUpDto + ): Promise { + const role: RoleDocument = await this.roleService.findOne( + { + name: 'user', + } + ); + if (!role) { + throw new NotFoundException({ + statusCode: ENUM_ROLE_STATUS_CODE_ERROR.ROLE_NOT_FOUND_ERROR, + message: 'role.error.notFound', + }); + } + + const checkExist: IUserCheckExist = await this.userService.checkExist( + email, + mobileNumber + ); + + if (checkExist.email && checkExist.mobileNumber) { + throw new BadRequestException({ + statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_EXISTS_ERROR, + message: 'user.error.exist', + }); + } else if (checkExist.email) { + throw new BadRequestException({ + statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_EMAIL_EXIST_ERROR, + message: 'user.error.emailExist', + }); + } else if (checkExist.mobileNumber) { + throw new BadRequestException({ + statusCode: + ENUM_USER_STATUS_CODE_ERROR.USER_MOBILE_NUMBER_EXIST_ERROR, + message: 'user.error.mobileNumberExist', + }); + } + + try { + const password = await this.authService.createPassword( + body.password + ); + + const create = await this.userService.create({ + firstName: body.firstName, + lastName: body.lastName, + email, + mobileNumber, + role: role._id, + password: password.passwordHash, + passwordExpired: password.passwordExpired, + salt: password.salt, + }); + + const user: IUserDocument = + await this.userService.findOneById(create._id, { + populate: { + role: true, + permission: true, + }, + }); + const safe: AuthLoginSerialization = + await this.authService.serializationLogin(user); + + const payloadAccessToken: Record = + await this.authService.createPayloadAccessToken(safe, false); + const payloadRefreshToken: Record = + await this.authService.createPayloadRefreshToken(safe, false, { + loginDate: payloadAccessToken.loginDate, + }); + + const accessToken: string = + await this.authService.createAccessToken(payloadAccessToken); + + const refreshToken: string = + await this.authService.createRefreshToken( + payloadRefreshToken, + false + ); + + return { + accessToken, + refreshToken, + }; + } catch (err: any) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + } +} diff --git a/packages/auth-api/src/auth/dto/auth.api.create.dto.ts b/packages/auth-api/src/auth/dto/auth.api.create.dto.ts new file mode 100644 index 0000000..8fa2ede --- /dev/null +++ b/packages/auth-api/src/auth/dto/auth.api.create.dto.ts @@ -0,0 +1,20 @@ +import { + IsNotEmpty, + IsOptional, + IsString, + MaxLength, + ValidateIf, +} from 'class-validator'; + +export class AuthApiCreateDto { + @IsNotEmpty() + @IsString() + @MaxLength(50) + name: string; + + @IsOptional() + @ValidateIf((e) => e.description !== '') + @IsString() + @MaxLength(100) + description?: string; +} diff --git a/packages/auth-api/src/auth/dto/auth.api.update.dto.ts b/packages/auth-api/src/auth/dto/auth.api.update.dto.ts new file mode 100644 index 0000000..5651856 --- /dev/null +++ b/packages/auth-api/src/auth/dto/auth.api.update.dto.ts @@ -0,0 +1,20 @@ +import { + IsNotEmpty, + IsOptional, + IsString, + MaxLength, + ValidateIf, +} from 'class-validator'; + +export class AuthApiUpdateDto { + @IsNotEmpty() + @IsString() + @MaxLength(50) + name: string; + + @IsOptional() + @ValidateIf((e) => e.description !== '') + @IsString() + @MaxLength(100) + description?: string; +} diff --git a/packages/auth-api/src/auth/dto/auth.change-password.dto.ts b/packages/auth-api/src/auth/dto/auth.change-password.dto.ts new file mode 100644 index 0000000..11e611d --- /dev/null +++ b/packages/auth-api/src/auth/dto/auth.change-password.dto.ts @@ -0,0 +1,12 @@ +import { IsString, IsNotEmpty } from 'class-validator'; +import { IsPasswordStrong } from 'src/utils/request/validation/request.is-password-strong.validation'; + +export class AuthChangePasswordDto { + @IsPasswordStrong() + @IsNotEmpty() + readonly newPassword: string; + + @IsString() + @IsNotEmpty() + readonly oldPassword: string; +} diff --git a/packages/auth-api/src/auth/dto/auth.login.dto.ts b/packages/auth-api/src/auth/dto/auth.login.dto.ts new file mode 100644 index 0000000..9b56856 --- /dev/null +++ b/packages/auth-api/src/auth/dto/auth.login.dto.ts @@ -0,0 +1,27 @@ +import { Type } from 'class-transformer'; +import { + IsNotEmpty, + IsEmail, + MaxLength, + IsBoolean, + IsOptional, + ValidateIf, + IsString, +} from 'class-validator'; + +export class AuthLoginDto { + @IsEmail() + @IsNotEmpty() + @MaxLength(100) + readonly email: string; + + @IsOptional() + @IsBoolean() + @ValidateIf((e) => e.rememberMe !== '') + readonly rememberMe?: boolean; + + @IsNotEmpty() + @Type(() => String) + @IsString() + readonly password: string; +} diff --git a/packages/auth-api/src/auth/dto/auth.sign-up.dto.ts b/packages/auth-api/src/auth/dto/auth.sign-up.dto.ts new file mode 100644 index 0000000..4dec78c --- /dev/null +++ b/packages/auth-api/src/auth/dto/auth.sign-up.dto.ts @@ -0,0 +1,47 @@ +import { Type } from 'class-transformer'; +import { + IsString, + IsNotEmpty, + IsEmail, + MaxLength, + MinLength, + IsOptional, + ValidateIf, +} from 'class-validator'; +import { IsPasswordStrong } from 'src/utils/request/validation/request.is-password-strong.validation'; +import { IsStartWith } from 'src/utils/request/validation/request.is-start-with.validation'; + +export class AuthSignUpDto { + @IsEmail() + @IsNotEmpty() + @MaxLength(100) + @Type(() => String) + readonly email: string; + + @IsString() + @IsNotEmpty() + @MinLength(1) + @MaxLength(30) + @Type(() => String) + readonly firstName: string; + + @IsString() + @IsOptional() + @ValidateIf((e) => e.lastName !== '') + @MinLength(1) + @MaxLength(30) + @Type(() => String) + readonly lastName?: string; + + @IsString() + @IsNotEmpty() + @MinLength(10) + @MaxLength(14) + @Type(() => String) + @IsStartWith(['628']) + readonly mobileNumber: string; + + @IsNotEmpty() + @IsPasswordStrong() + readonly password: string; +} diff --git a/packages/auth-api/src/auth/guard/api-key/auth.api-key.guard.ts b/packages/auth-api/src/auth/guard/api-key/auth.api-key.guard.ts new file mode 100644 index 0000000..36c13f7 --- /dev/null +++ b/packages/auth-api/src/auth/guard/api-key/auth.api-key.guard.ts @@ -0,0 +1,117 @@ +import { AuthGuard } from '@nestjs/passport'; +import { + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { + AUTH_EXCLUDE_API_KEY_META_KEY, + ENUM_AUTH_STATUS_CODE_ERROR, +} from 'src/auth/auth.constant'; +import { ConfigService } from '@nestjs/config'; +import { Reflector } from '@nestjs/core'; + +@Injectable() +export class ApiKeyGuard extends AuthGuard('api-key') { + constructor( + private readonly configService: ConfigService, + private readonly reflector: Reflector + ) { + super(); + } + + canActivate(context: ExecutionContext) { + const mode = this.configService.get('app.mode'); + const excludeApiKey = this.reflector.get( + AUTH_EXCLUDE_API_KEY_META_KEY, + context.getHandler() + ); + + const request = context.switchToHttp().getRequest(); + request.apiKey = {}; + + if (excludeApiKey || mode !== 'secure') { + return true; + } + + return super.canActivate(context); + } + + handleRequest( + err: Record, + user: TUser, + info: Error | string + ): TUser { + if (err || !user) { + if ( + info instanceof Error && + info.name === 'BadRequestError' && + info.message === 'Missing API Key' + ) { + throw new UnauthorizedException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_NEEDED_ERROR, + message: 'auth.apiKey.error.keyNeeded', + }); + } else if ( + info instanceof Error && + info.name === 'BadRequestError' && + info.message.startsWith('Invalid API Key prefix') + ) { + throw new UnauthorizedException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_PREFIX_INVALID_ERROR, + message: 'auth.apiKey.error.prefixInvalid', + }); + } + + const statusCode: number = Number.parseInt(info as string); + + if ( + statusCode === + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_SCHEMA_INVALID_ERROR + ) { + throw new UnauthorizedException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_SCHEMA_INVALID_ERROR, + message: 'auth.apiKey.error.schemaInvalid', + }); + } else if ( + statusCode === + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_TIMESTAMP_NOT_MATCH_WITH_REQUEST_ERROR + ) { + throw new UnauthorizedException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_TIMESTAMP_NOT_MATCH_WITH_REQUEST_ERROR, + message: 'auth.apiKey.error.timestampNotMatchWithRequest', + }); + } else if ( + statusCode === + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_NOT_FOUND_ERROR + ) { + throw new UnauthorizedException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_NOT_FOUND_ERROR, + message: 'auth.apiKey.error.notFound', + }); + } else if ( + statusCode === + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_INACTIVE_ERROR + ) { + throw new UnauthorizedException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_INACTIVE_ERROR, + message: 'auth.apiKey.error.inactive', + }); + } + + throw new UnauthorizedException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_INVALID_ERROR, + message: 'auth.apiKey.error.invalid', + }); + } + + return user; + } +} diff --git a/packages/auth-api/src/auth/guard/api-key/auth.api-key.strategy.ts b/packages/auth-api/src/auth/guard/api-key/auth.api-key.strategy.ts new file mode 100644 index 0000000..424694f --- /dev/null +++ b/packages/auth-api/src/auth/guard/api-key/auth.api-key.strategy.ts @@ -0,0 +1,114 @@ +import { Injectable } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import Strategy from 'passport-headerapikey'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; +import { AuthApiDocument } from 'src/auth/schema/auth.api.schema'; +import { IAuthApiRequestHashedData } from 'src/auth/auth.interface'; +import { ENUM_AUTH_STATUS_CODE_ERROR } from 'src/auth/auth.constant'; +import { IRequestApp } from 'src/utils/request/request.interface'; + +@Injectable() +export class ApiKeyStrategy extends PassportStrategy(Strategy, 'api-key') { + constructor(private readonly authApiService: AuthApiService) { + super( + { header: 'X-API-KEY', prefix: '' }, + true, + async ( + apiKey: string, + verified: ( + error: Error, + user?: Record, + info?: string | number + ) => Promise, + req: IRequestApp + ) => this.validate(apiKey, verified, req) + ); + } + + async validate( + apiKey: string, + verified: ( + error: Error, + user?: TUser, + info?: string | number + ) => Promise, + req: any + ) { + const xApiKey: string[] = apiKey.split(':'); + const key = xApiKey[0]; + const encrypted = xApiKey[1]; + + const authApi: AuthApiDocument = await this.authApiService.findOneByKey( + key + ); + if (!authApi) { + verified( + null, + null, + `${ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_NOT_FOUND_ERROR}` + ); + } else if (!authApi.isActive) { + verified( + null, + null, + `${ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_INACTIVE_ERROR}` + ); + } + + const decrypted: IAuthApiRequestHashedData = + await this.authApiService.decryptApiKey( + encrypted, + authApi.encryptionKey, + authApi.passphrase + ); + + const keys: string[] = ['key', 'timestamp', 'hash']; + const deKeys: string[] = Object.keys(decrypted); + const hasKey: boolean = keys.every((key) => deKeys.includes(key)); + + const timestamp: number = Number.parseInt( + req.headers['x-timestamp'] as string + ); + + if (!hasKey) { + verified( + null, + null, + `${ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_SCHEMA_INVALID_ERROR}` + ); + } else if (key !== decrypted.key) { + verified( + null, + null, + `${ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_INVALID_ERROR}` + ); + } else if (timestamp !== decrypted.timestamp) { + verified( + new Error( + `${ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_TIMESTAMP_NOT_MATCH_WITH_REQUEST_ERROR}` + ) + ); + } + + const validateApiKey: boolean = + await this.authApiService.validateHashApiKey( + decrypted.hash, + authApi.hash + ); + if (!validateApiKey) { + verified( + null, + null, + `${ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_API_KEY_INVALID_ERROR}` + ); + } + + req.apiKey = { + _id: authApi._id, + key: authApi.key, + name: authApi.name, + description: authApi.description, + }; + verified(null, authApi as any); + } +} diff --git a/packages/auth-api/src/auth/guard/basic/auth.basic.guard.ts b/packages/auth-api/src/auth/guard/basic/auth.basic.guard.ts new file mode 100644 index 0000000..0cd6510 --- /dev/null +++ b/packages/auth-api/src/auth/guard/basic/auth.basic.guard.ts @@ -0,0 +1,64 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + UnauthorizedException, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { ENUM_AUTH_STATUS_CODE_ERROR } from 'src/auth/auth.constant'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; +import { IRequestApp } from 'src/utils/request/request.interface'; + +@Injectable() +export class BasicGuard implements CanActivate { + private readonly clientId: string; + private readonly clientSecret: string; + + constructor( + private readonly configService: ConfigService, + private readonly authApiService: AuthApiService + ) { + this.clientId = this.configService.get( + 'auth.basicToken.clientId' + ); + this.clientSecret = this.configService.get( + 'auth.basicToken.clientSecret' + ); + } + + async canActivate(context: ExecutionContext): Promise { + const request: IRequestApp = context.switchToHttp().getRequest(); + const authorization: string = request.headers.authorization; + + if (!authorization) { + throw new UnauthorizedException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_BASIC_TOKEN_NEEDED_ERROR, + message: 'http.clientError.unauthorized', + }); + } + + const clientBasicToken: string = authorization.replace('Basic ', ''); + const ourBasicToken: string = + await this.authApiService.createBasicToken( + this.clientId, + this.clientSecret + ); + + const validateBasicToken: boolean = + await this.authApiService.validateBasicToken( + clientBasicToken, + ourBasicToken + ); + + if (!validateBasicToken) { + throw new UnauthorizedException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_BASIC_TOKEN_INVALID_ERROR, + message: 'http.clientError.unauthorized', + }); + } + + return true; + } +} diff --git a/packages/auth-api/src/auth/guard/jwt-refresh/auth.jwt-refresh.guard.ts b/packages/auth-api/src/auth/guard/jwt-refresh/auth.jwt-refresh.guard.ts new file mode 100644 index 0000000..e64aee5 --- /dev/null +++ b/packages/auth-api/src/auth/guard/jwt-refresh/auth.jwt-refresh.guard.ts @@ -0,0 +1,18 @@ +import { AuthGuard } from '@nestjs/passport'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { ENUM_AUTH_STATUS_CODE_ERROR } from 'src/auth/auth.constant'; + +@Injectable() +export class JwtRefreshGuard extends AuthGuard('jwtRefresh') { + handleRequest(err: Record, user: TUser): TUser { + if (err || !user) { + throw new UnauthorizedException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_JWT_REFRESH_TOKEN_ERROR, + message: 'http.clientError.unauthorized', + }); + } + + return user; + } +} diff --git a/packages/auth-api/src/auth/guard/jwt-refresh/auth.jwt-refresh.strategy.ts b/packages/auth-api/src/auth/guard/jwt-refresh/auth.jwt-refresh.strategy.ts new file mode 100644 index 0000000..6809860 --- /dev/null +++ b/packages/auth-api/src/auth/guard/jwt-refresh/auth.jwt-refresh.strategy.ts @@ -0,0 +1,27 @@ +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class JwtRefreshStrategy extends PassportStrategy( + Strategy, + 'jwtRefresh' +) { + constructor(private readonly configService: ConfigService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + jsonWebTokenOptions: { + ignoreNotBefore: false, + }, + secretOrKey: configService.get( + 'auth.jwt.refreshToken.secretKey' + ), + }); + } + + async validate(payload: Record): Promise> { + return payload; + } +} diff --git a/packages/auth-api/src/auth/guard/jwt/auth.jwt.guard.ts b/packages/auth-api/src/auth/guard/jwt/auth.jwt.guard.ts new file mode 100644 index 0000000..818fec6 --- /dev/null +++ b/packages/auth-api/src/auth/guard/jwt/auth.jwt.guard.ts @@ -0,0 +1,18 @@ +import { AuthGuard } from '@nestjs/passport'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { ENUM_AUTH_STATUS_CODE_ERROR } from 'src/auth/auth.constant'; + +@Injectable() +export class JwtGuard extends AuthGuard('jwt') { + handleRequest(err: Record, user: TUser): TUser { + if (err || !user) { + throw new UnauthorizedException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_JWT_ACCESS_TOKEN_ERROR, + message: 'http.clientError.unauthorized', + }); + } + + return user; + } +} diff --git a/packages/auth-api/src/auth/guard/jwt/auth.jwt.strategy.ts b/packages/auth-api/src/auth/guard/jwt/auth.jwt.strategy.ts new file mode 100644 index 0000000..e3e0d5d --- /dev/null +++ b/packages/auth-api/src/auth/guard/jwt/auth.jwt.strategy.ts @@ -0,0 +1,24 @@ +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { + constructor(private readonly configService: ConfigService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + jsonWebTokenOptions: { + ignoreNotBefore: false, + }, + secretOrKey: configService.get( + 'auth.jwt.accessToken.secretKey' + ), + }); + } + + async validate(payload: Record): Promise> { + return payload; + } +} diff --git a/packages/auth-api/src/auth/guard/payload/auth.payload.admin.guard.ts b/packages/auth-api/src/auth/guard/payload/auth.payload.admin.guard.ts new file mode 100644 index 0000000..dea7b4d --- /dev/null +++ b/packages/auth-api/src/auth/guard/payload/auth.payload.admin.guard.ts @@ -0,0 +1,36 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + ForbiddenException, +} from '@nestjs/common'; +import { + AUTH_ADMIN_META_KEY, + ENUM_AUTH_STATUS_CODE_ERROR, +} from 'src/auth/auth.constant'; +import { Reflector } from '@nestjs/core'; + +@Injectable() +export class AuthPayloadAdminGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + async canActivate(context: ExecutionContext): Promise { + const required: boolean[] = this.reflector.getAllAndOverride( + AUTH_ADMIN_META_KEY, + [context.getHandler(), context.getClass()] + ); + + if (!required) { + return true; + } + + const { user } = context.switchToHttp().getRequest(); + if (!required.includes(user.role.isAdmin)) { + throw new ForbiddenException({ + statusCode: ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_ADMIN_ERROR, + message: 'auth.error.admin', + }); + } + return true; + } +} diff --git a/packages/auth-api/src/auth/guard/payload/auth.payload.default.guard.ts b/packages/auth-api/src/auth/guard/payload/auth.payload.default.guard.ts new file mode 100644 index 0000000..b7b09cf --- /dev/null +++ b/packages/auth-api/src/auth/guard/payload/auth.payload.default.guard.ts @@ -0,0 +1,30 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + ForbiddenException, +} from '@nestjs/common'; +import { ENUM_AUTH_STATUS_CODE_ERROR } from 'src/auth/auth.constant'; + +@Injectable() +export class AuthPayloadDefaultGuard implements CanActivate { + async canActivate(context: ExecutionContext): Promise { + const { user } = context.switchToHttp().getRequest(); + + if (!user.isActive) { + throw new ForbiddenException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_INACTIVE_ERROR, + message: 'auth.error.blocked', + }); + } else if (!user.role.isActive) { + throw new ForbiddenException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_ROLE_INACTIVE_ERROR, + message: 'auth.error.roleBlocked', + }); + } + + return true; + } +} diff --git a/packages/auth-api/src/auth/guard/payload/auth.payload.password-expired.guard.ts b/packages/auth-api/src/auth/guard/payload/auth.payload.password-expired.guard.ts new file mode 100644 index 0000000..9da4811 --- /dev/null +++ b/packages/auth-api/src/auth/guard/payload/auth.payload.password-expired.guard.ts @@ -0,0 +1,32 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + ForbiddenException, +} from '@nestjs/common'; +import { ENUM_AUTH_STATUS_CODE_ERROR } from 'src/auth/auth.constant'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; + +@Injectable() +export class AuthPayloadPasswordExpiredGuard implements CanActivate { + constructor(private readonly helperDateService: HelperDateService) {} + + async canActivate(context: ExecutionContext): Promise { + const { user } = context.switchToHttp().getRequest(); + const { passwordExpired } = user; + const today: Date = this.helperDateService.create(); + const passwordExpiredDate = this.helperDateService.create({ + date: passwordExpired, + }); + + if (today > passwordExpiredDate) { + throw new ForbiddenException({ + statusCode: + ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GUARD_PASSWORD_EXPIRED_ERROR, + message: 'auth.error.passwordExpired', + }); + } + + return true; + } +} diff --git a/packages/auth-api/src/auth/schema/auth.api.schema.ts b/packages/auth-api/src/auth/schema/auth.api.schema.ts new file mode 100644 index 0000000..de2e79d --- /dev/null +++ b/packages/auth-api/src/auth/schema/auth.api.schema.ts @@ -0,0 +1,52 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Document } from 'mongoose'; + +@Schema({ timestamps: true, versionKey: false }) +export class AuthApiEntity { + @Prop({ + required: true, + }) + name: string; + + @Prop({ + required: false, + }) + description?: string; + + @Prop({ + required: true, + trim: true, + unique: true, + }) + key: string; + + @Prop({ + required: true, + trim: true, + }) + hash: string; + + @Prop({ + required: true, + trim: true, + }) + encryptionKey: string; + + @Prop({ + required: true, + trim: true, + minLength: 16, + maxLength: 16, + }) + passphrase: string; + + @Prop({ + required: true, + }) + isActive: boolean; +} + +export const AuthApiDatabaseName = 'authapis'; +export const AuthApiSchema = SchemaFactory.createForClass(AuthApiEntity); + +export type AuthApiDocument = AuthApiEntity & Document; diff --git a/packages/auth-api/src/auth/serialization/auth.api.get.serialization.ts b/packages/auth-api/src/auth/serialization/auth.api.get.serialization.ts new file mode 100644 index 0000000..16ec7f4 --- /dev/null +++ b/packages/auth-api/src/auth/serialization/auth.api.get.serialization.ts @@ -0,0 +1,20 @@ +import { Exclude, Type } from 'class-transformer'; + +export class AuthApiGetSerialization { + @Type(() => String) + readonly _id: string; + + readonly name: string; + readonly description?: string; + readonly key: string; + + @Exclude() + readonly hash: string; + + readonly encryptionKey: string; + readonly passphrase: string; + + readonly isActive: boolean; + readonly createdAt: Date; + readonly updatedAt: Date; +} diff --git a/packages/auth-api/src/auth/serialization/auth.api.list.serialization.ts b/packages/auth-api/src/auth/serialization/auth.api.list.serialization.ts new file mode 100644 index 0000000..6953024 --- /dev/null +++ b/packages/auth-api/src/auth/serialization/auth.api.list.serialization.ts @@ -0,0 +1,11 @@ +import { Type } from 'class-transformer'; + +export class AuthApiListSerialization { + @Type(() => String) + readonly _id: string; + + readonly name: string; + readonly key: string; + readonly isActive: boolean; + readonly createdAt: Date; +} diff --git a/packages/auth-api/src/auth/serialization/auth.login.serialization.ts b/packages/auth-api/src/auth/serialization/auth.login.serialization.ts new file mode 100644 index 0000000..527902b --- /dev/null +++ b/packages/auth-api/src/auth/serialization/auth.login.serialization.ts @@ -0,0 +1,47 @@ +import { Exclude, Transform, Type } from 'class-transformer'; +import { IAwsS3Response } from 'src/aws/aws.interface'; +import { IRoleDocument } from 'src/role/role.interface'; + +export class AuthLoginSerialization { + @Type(() => String) + readonly _id: string; + + @Transform(({ value }) => ({ + name: value.name, + permissions: value.permissions.map((val: Record) => ({ + code: val.code, + isActive: val.isActive, + })), + isActive: value.isActive, + isAdmin: value.isAdmin, + })) + readonly role: IRoleDocument; + + readonly email: string; + readonly mobileNumber: string; + readonly isActive: boolean; + readonly passwordExpired: Date; + readonly loginDate: Date; + readonly rememberMe: boolean; + + @Exclude() + readonly firstName: string; + + @Exclude() + readonly lastName: string; + + @Exclude() + readonly photo?: IAwsS3Response; + + @Exclude() + readonly password: string; + + @Exclude() + readonly salt: string; + + @Exclude() + readonly createdAt: Date; + + @Exclude() + readonly updatedAt: Date; +} diff --git a/packages/auth-api/src/auth/service/auth.api.bulk.service.ts b/packages/auth-api/src/auth/service/auth.api.bulk.service.ts new file mode 100644 index 0000000..f54388b --- /dev/null +++ b/packages/auth-api/src/auth/service/auth.api.bulk.service.ts @@ -0,0 +1,18 @@ +/* istanbul ignore file */ + +import { Injectable } from '@nestjs/common'; +import { DatabaseEntity } from 'src/database/database.decorator'; +import { Model } from 'mongoose'; +import { AuthApiDocument, AuthApiEntity } from '../schema/auth.api.schema'; + +@Injectable() +export class AuthApiBulkService { + constructor( + @DatabaseEntity(AuthApiEntity.name) + private readonly authApiModel: Model + ) {} + + async deleteMany(find: Record) { + return this.authApiModel.deleteMany(find); + } +} diff --git a/packages/auth-api/src/auth/service/auth.api.service.ts b/packages/auth-api/src/auth/service/auth.api.service.ts new file mode 100644 index 0000000..4ab19b5 --- /dev/null +++ b/packages/auth-api/src/auth/service/auth.api.service.ts @@ -0,0 +1,265 @@ +/* istanbul ignore file */ + +import { Injectable } from '@nestjs/common'; +import { DatabaseEntity } from 'src/database/database.decorator'; +import { Model } from 'mongoose'; +import { AuthApiDocument, AuthApiEntity } from '../schema/auth.api.schema'; +import { IDatabaseFindAllOptions } from 'src/database/database.interface'; +import { plainToInstance } from 'class-transformer'; +import { AuthApiListSerialization } from '../serialization/auth.api.list.serialization'; +import { AuthApiGetSerialization } from '../serialization/auth.api.get.serialization'; +import { + IAuthApiDocument, + IAuthApiRequestHashedData, + IAuthApiCreate, +} from '../auth.interface'; +import { HelperStringService } from 'src/utils/helper/service/helper.string.service'; +import { ConfigService } from '@nestjs/config'; +import { HelperHashService } from 'src/utils/helper/service/helper.hash.service'; +import { AuthApiUpdateDto } from '../dto/auth.api.update.dto'; +import { HelperEncryptionService } from 'src/utils/helper/service/helper.encryption.service'; + +@Injectable() +export class AuthApiService { + private readonly env: string; + + constructor( + @DatabaseEntity(AuthApiEntity.name) + private readonly authApiModel: Model, + private readonly helperStringService: HelperStringService, + private readonly configService: ConfigService, + private readonly helperHashService: HelperHashService, + private readonly helperEncryptionService: HelperEncryptionService + ) { + this.env = this.configService.get('app.env'); + } + + async findAll( + find?: Record, + options?: IDatabaseFindAllOptions + ): Promise { + const users = this.authApiModel.find(find).select({ + name: 1, + key: 1, + isActive: 1, + createdAt: 1, + }); + + if ( + options && + options.limit !== undefined && + options.skip !== undefined + ) { + users.limit(options.limit).skip(options.skip); + } + + if (options && options.sort) { + users.sort(options.sort); + } + + return users.lean(); + } + + async getTotal(find?: Record): Promise { + return this.authApiModel.countDocuments(find); + } + + async findOneById(_id: string): Promise { + return this.authApiModel.findById(_id).lean(); + } + + async findOne(find?: Record): Promise { + return this.authApiModel.findOne(find).lean(); + } + + async findOneByKey(key: string): Promise { + return this.authApiModel.findOne({ key }).lean(); + } + + async serializationList( + data: AuthApiDocument[] + ): Promise { + return plainToInstance(AuthApiListSerialization, data); + } + + async serializationGet( + data: AuthApiDocument + ): Promise { + return plainToInstance(AuthApiGetSerialization, data); + } + + async inactive(_id: string): Promise { + const authApi: AuthApiDocument = await this.authApiModel.findById(_id); + + authApi.isActive = false; + return authApi.save(); + } + + async active(_id: string): Promise { + const authApi: AuthApiDocument = await this.authApiModel.findById(_id); + + authApi.isActive = true; + return authApi.save(); + } + + async create({ + name, + description, + key, + secret, + passphrase, + encryptionKey, + }: IAuthApiCreate): Promise { + key = key ? key : await this.createKey(); + secret = secret ? secret : await this.createSecret(); + passphrase = passphrase ? passphrase : await this.createPassphrase(); + encryptionKey = encryptionKey + ? encryptionKey + : await this.createEncryptionKey(); + const hash: string = await this.createHashApiKey(key, secret); + + const create: AuthApiDocument = new this.authApiModel({ + name, + description, + key, + hash, + passphrase, + encryptionKey, + isActive: true, + }); + + await create.save(); + + return { + _id: create._id, + secret, + passphrase, + encryptionKey, + }; + } + + async updateOneById( + _id: string, + { name, description }: AuthApiUpdateDto + ): Promise { + const authApi: AuthApiDocument = await this.authApiModel.findById(_id); + + authApi.name = name; + authApi.description = description; + + return authApi.save(); + } + + async updateHashById(_id: string): Promise { + const authApi: AuthApiDocument = await this.authApiModel.findById(_id); + const secret: string = await this.createSecret(); + const hash: string = await this.createHashApiKey(authApi.key, secret); + const passphrase: string = await this.createPassphrase(); + const encryptionKey: string = await this.createEncryptionKey(); + + authApi.hash = hash; + authApi.passphrase = passphrase; + authApi.encryptionKey = encryptionKey; + + await authApi.save(); + + return { + _id: authApi._id, + secret, + passphrase, + encryptionKey, + }; + } + + async deleteOneById(_id: string): Promise { + return this.authApiModel.findByIdAndDelete(_id); + } + + async deleteOne(find: Record): Promise { + return this.authApiModel.findOneAndDelete(find); + } + + async createKey(): Promise { + return this.helperStringService.random(25, { + safe: false, + upperCase: true, + prefix: this.env === 'production' ? 'production_' : 'development_', + }); + } + + async createEncryptionKey(): Promise { + return this.helperStringService.random(15, { + safe: false, + upperCase: true, + prefix: this.env === 'production' ? 'production_' : 'development_', + }); + } + + async createSecret(): Promise { + return this.helperStringService.random(35, { + safe: false, + upperCase: true, + }); + } + + async createPassphrase(): Promise { + return this.helperStringService.random(16, { + safe: true, + }); + } + + async createHashApiKey(key: string, secret: string): Promise { + return this.helperHashService.sha256(`${key}:${secret}`); + } + + async validateHashApiKey( + hashFromRequest: string, + hash: string + ): Promise { + return this.helperHashService.sha256Compare(hashFromRequest, hash); + } + + async decryptApiKey( + apiKeyHashed: string, + secretKey: string, + passphrase: string + ): Promise { + const decrypted = this.helperEncryptionService.aes256Decrypt( + apiKeyHashed, + secretKey, + passphrase + ); + + return JSON.parse(decrypted); + } + + async encryptApiKey( + data: IAuthApiRequestHashedData, + secretKey: string, + passphrase: string + ): Promise { + return this.helperEncryptionService.aes256Encrypt( + data, + secretKey, + passphrase + ); + } + + async createBasicToken( + clientId: string, + clientSecret: string + ): Promise { + const token = `${clientId}:${clientSecret}`; + return this.helperEncryptionService.base64Decrypt(token); + } + + async validateBasicToken( + clientBasicToken: string, + ourBasicToken: string + ): Promise { + return this.helperEncryptionService.base64Compare( + clientBasicToken, + ourBasicToken + ); + } +} diff --git a/packages/auth-api/src/auth/service/auth.service.ts b/packages/auth-api/src/auth/service/auth.service.ts new file mode 100644 index 0000000..ed01cc0 --- /dev/null +++ b/packages/auth-api/src/auth/service/auth.service.ts @@ -0,0 +1,177 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { plainToInstance } from 'class-transformer'; +import { IUserDocument } from 'src/user/user.interface'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { HelperEncryptionService } from 'src/utils/helper/service/helper.encryption.service'; +import { HelperHashService } from 'src/utils/helper/service/helper.hash.service'; +import { IAuthPassword, IAuthPayloadOptions } from '../auth.interface'; +import { AuthLoginDto } from '../dto/auth.login.dto'; +import { AuthLoginSerialization } from '../serialization/auth.login.serialization'; + +@Injectable() +export class AuthService { + private readonly accessTokenSecretToken: string; + private readonly accessTokenExpirationTime: string; + private readonly accessTokenNotBeforeExpirationTime: string; + + private readonly refreshTokenSecretToken: string; + private readonly refreshTokenExpirationTime: string; + private readonly refreshTokenExpirationTimeRememberMe: string; + private readonly refreshTokenNotBeforeExpirationTime: string; + + constructor( + private readonly helperHashService: HelperHashService, + private readonly helperDateService: HelperDateService, + private readonly helperEncryptionService: HelperEncryptionService, + private readonly configService: ConfigService + ) { + this.accessTokenSecretToken = this.configService.get( + 'auth.jwt.accessToken.secretKey' + ); + this.accessTokenExpirationTime = this.configService.get( + 'auth.jwt.accessToken.expirationTime' + ); + this.accessTokenNotBeforeExpirationTime = + this.configService.get( + 'auth.jwt.accessToken.notBeforeExpirationTime' + ); + + this.refreshTokenSecretToken = this.configService.get( + 'auth.jwt.refreshToken.secretKey' + ); + this.refreshTokenExpirationTime = this.configService.get( + 'auth.jwt.refreshToken.expirationTime' + ); + this.refreshTokenExpirationTimeRememberMe = + this.configService.get( + 'auth.jwt.refreshToken.expirationTimeRememberMe' + ); + this.refreshTokenNotBeforeExpirationTime = + this.configService.get( + 'auth.jwt.refreshToken.notBeforeExpirationTime' + ); + } + + async createAccessToken(payload: Record): Promise { + return this.helperEncryptionService.jwtEncrypt(payload, { + secretKey: this.accessTokenSecretToken, + expiredIn: this.accessTokenExpirationTime, + notBefore: this.accessTokenNotBeforeExpirationTime, + }); + } + + async validateAccessToken(token: string): Promise { + return this.helperEncryptionService.jwtVerify(token, { + secretKey: this.accessTokenSecretToken, + expiredIn: this.accessTokenExpirationTime, + }); + } + + async payloadAccessToken(token: string): Promise> { + return this.helperEncryptionService.jwtDecrypt(token); + } + + async createRefreshToken( + payload: Record, + rememberMe: boolean, + test?: boolean + ): Promise { + return this.helperEncryptionService.jwtEncrypt(payload, { + secretKey: this.refreshTokenSecretToken, + expiredIn: rememberMe + ? this.refreshTokenExpirationTimeRememberMe + : this.refreshTokenExpirationTime, + notBefore: test ? '0' : this.refreshTokenNotBeforeExpirationTime, + }); + } + + async validateRefreshToken(token: string): Promise { + return this.helperEncryptionService.jwtVerify(token, { + secretKey: this.refreshTokenSecretToken, + expiredIn: this.accessTokenExpirationTime, + }); + } + + async payloadRefreshToken(token: string): Promise> { + return this.helperEncryptionService.jwtDecrypt(token); + } + + async validateUser( + passwordString: string, + passwordHash: string + ): Promise { + return this.helperHashService.bcryptCompare( + passwordString, + passwordHash + ); + } + + async createPayloadAccessToken( + data: AuthLoginDto, + rememberMe: boolean, + options?: IAuthPayloadOptions + ): Promise> { + return { + ...data, + rememberMe, + loginDate: + options && options.loginDate + ? options.loginDate + : this.helperDateService.create(), + }; + } + + async createPayloadRefreshToken( + { _id }: AuthLoginSerialization, + rememberMe: boolean, + options?: IAuthPayloadOptions + ): Promise> { + return { + _id, + rememberMe, + loginDate: + options && options.loginDate ? options.loginDate : undefined, + }; + } + + async serializationLogin( + data: IUserDocument + ): Promise { + return plainToInstance(AuthLoginSerialization, data); + } + + async createPassword(password: string): Promise { + const saltLength: number = this.configService.get( + 'auth.password.saltLength' + ); + + const salt: string = this.helperHashService.randomSalt(saltLength); + + const passwordExpiredInDays: number = this.configService.get( + 'auth.password.expiredInDay' + ); + const passwordExpired: Date = this.helperDateService.forwardInDays( + passwordExpiredInDays + ); + const passwordHash = this.helperHashService.bcrypt(password, salt); + return { + passwordHash, + passwordExpired, + salt, + }; + } + + async checkPasswordExpired(passwordExpired: Date): Promise { + const today: Date = this.helperDateService.create(); + const passwordExpiredConvert: Date = this.helperDateService.create({ + date: passwordExpired, + }); + + if (today > passwordExpiredConvert) { + return true; + } + + return false; + } +} diff --git a/packages/auth-api/src/aws/aws.interface.ts b/packages/auth-api/src/aws/aws.interface.ts new file mode 100644 index 0000000..c3dbfdc --- /dev/null +++ b/packages/auth-api/src/aws/aws.interface.ts @@ -0,0 +1,15 @@ +import { ObjectCannedACL } from '@aws-sdk/client-s3'; + +export interface IAwsS3Response { + path: string; + pathWithFilename: string; + filename: string; + completedUrl: string; + baseUrl: string; + mime: string; +} + +export interface IAwsS3PutItemOptions { + path: string; + acl?: ObjectCannedACL; +} diff --git a/packages/auth-api/src/aws/aws.module.ts b/packages/auth-api/src/aws/aws.module.ts new file mode 100644 index 0000000..7f88865 --- /dev/null +++ b/packages/auth-api/src/aws/aws.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { AwsS3Service } from './service/aws.s3.service'; + +@Module({ + exports: [AwsS3Service], + providers: [AwsS3Service], + imports: [], + controllers: [], +}) +export class AwsModule {} diff --git a/packages/auth-api/src/aws/service/aws.s3.service.ts b/packages/auth-api/src/aws/service/aws.s3.service.ts new file mode 100644 index 0000000..fbe860c --- /dev/null +++ b/packages/auth-api/src/aws/service/aws.s3.service.ts @@ -0,0 +1,201 @@ +import { + S3Client, + GetObjectCommand, + ListBucketsCommand, + ListObjectsV2Command, + PutObjectCommand, + DeleteObjectCommand, + DeleteObjectsCommand, + ObjectIdentifier, +} from '@aws-sdk/client-s3'; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { Readable } from 'stream'; +import { IAwsS3PutItemOptions, IAwsS3Response } from '../aws.interface'; + +@Injectable() +export class AwsS3Service { + private readonly s3Client: S3Client; + private readonly bucket: string; + private readonly baseUrl: string; + + constructor(private readonly configService: ConfigService) { + this.s3Client = new S3Client({ + credentials: { + accessKeyId: + this.configService.get('aws.credential.key'), + secretAccessKey: this.configService.get( + 'aws.credential.secret' + ), + }, + region: this.configService.get('aws.s3.region'), + }); + + this.bucket = this.configService.get('aws.s3.bucket'); + this.baseUrl = this.configService.get('aws.s3.baseUrl'); + } + + async listBucket(): Promise { + const command: ListBucketsCommand = new ListBucketsCommand({}); + const listBucket: Record = await this.s3Client.send( + command + ); + return listBucket.Buckets.map((val: Record) => val.Name); + } + + async listItemInBucket(prefix?: string): Promise { + const command: ListObjectsV2Command = new ListObjectsV2Command({ + Bucket: this.bucket, + Prefix: prefix, + }); + const listItems: Record = await this.s3Client.send( + command + ); + + return listItems.Contents.map((val: Record) => { + const lastIndex: number = val.Key.lastIndexOf('/'); + const path: string = val.Key.substring(0, lastIndex); + const filename: string = val.Key.substring( + lastIndex, + val.Key.length + ); + const mime: string = filename + .substring(filename.lastIndexOf('.') + 1, filename.length) + .toLocaleUpperCase(); + + return { + path, + pathWithFilename: val.Key, + filename: filename, + completedUrl: `${this.baseUrl}/${val.Key}`, + baseUrl: this.baseUrl, + mime, + }; + }); + } + + async getItemInBucket( + filename: string, + path?: string + ): Promise> { + if (path) + path = path.startsWith('/') ? path.replace('/', '') : `${path}`; + + const key: string = path ? `${path}/${filename}` : filename; + const command: GetObjectCommand = new GetObjectCommand({ + Bucket: this.bucket, + Key: key, + }); + + const item: Record = await this.s3Client.send(command); + + return item.Body; + } + + async putItemInBucket( + filename: string, + content: + | string + | Uint8Array + | Buffer + | Readable + | ReadableStream + | Blob, + options?: IAwsS3PutItemOptions + ): Promise { + let path: string = options && options.path ? options.path : undefined; + const acl: string = + options && options.acl ? options.acl : 'public-read'; + + if (path) + path = path.startsWith('/') ? path.replace('/', '') : `${path}`; + + const mime: string = filename + .substring(filename.lastIndexOf('.') + 1, filename.length) + .toUpperCase(); + const key: string = path ? `${path}/${filename}` : filename; + const command: PutObjectCommand = new PutObjectCommand({ + Bucket: this.bucket, + Key: key, + Body: content, + ACL: acl, + }); + + await this.s3Client.send(command); + + return { + path, + pathWithFilename: key, + filename: filename, + completedUrl: `${this.baseUrl}/${key}`, + baseUrl: this.baseUrl, + mime, + }; + } + + async deleteItemInBucket(filename: string): Promise { + const command: DeleteObjectCommand = new DeleteObjectCommand({ + Bucket: this.bucket, + Key: filename, + }); + + try { + await this.s3Client.send(command); + return true; + } catch (e) { + return false; + } + } + + async deleteItemsInBucket(filenames: string[]): Promise { + const keys: ObjectIdentifier[] = filenames.map((val) => ({ + Key: val, + })); + const command: DeleteObjectsCommand = new DeleteObjectsCommand({ + Bucket: this.bucket, + Delete: { + Objects: keys, + }, + }); + + try { + await this.s3Client.send(command); + return true; + } catch (e) { + return false; + } + } + + async deleteFolder(dir: string): Promise { + const commandList: ListObjectsV2Command = new ListObjectsV2Command({ + Bucket: this.bucket, + Prefix: dir, + }); + const lists = await this.s3Client.send(commandList); + + try { + const listItems = lists.Contents.map((val) => ({ + Key: val.Key, + })); + const commandDeleteItems: DeleteObjectsCommand = + new DeleteObjectsCommand({ + Bucket: this.bucket, + Delete: { + Objects: listItems, + }, + }); + + await this.s3Client.send(commandDeleteItems); + + const commandDelete: DeleteObjectCommand = new DeleteObjectCommand({ + Bucket: this.bucket, + Key: dir, + }); + await this.s3Client.send(commandDelete); + + return true; + } catch (e) { + return false; + } + } +} diff --git a/packages/auth-api/src/cache/cache.module.ts b/packages/auth-api/src/cache/cache.module.ts new file mode 100644 index 0000000..66ccd05 --- /dev/null +++ b/packages/auth-api/src/cache/cache.module.ts @@ -0,0 +1,23 @@ +import { + CacheModule as NestJsCacheModule, + Global, + Module, +} from '@nestjs/common'; +import { CacheOptionsService } from './service/cache.options.service'; +import { CacheService } from './service/cache.service'; + +@Global() +@Module({ + controllers: [], + providers: [CacheOptionsService, CacheService], + exports: [CacheOptionsService, CacheService], + imports: [ + NestJsCacheModule.registerAsync({ + inject: [CacheOptionsService], + imports: [CacheModule], + useFactory: (cacheOptionsService: CacheOptionsService) => + cacheOptionsService.createCacheOptions(), + }), + ], +}) +export class CacheModule {} diff --git a/packages/auth-api/src/cache/service/cache.options.service.ts b/packages/auth-api/src/cache/service/cache.options.service.ts new file mode 100644 index 0000000..45eb0a9 --- /dev/null +++ b/packages/auth-api/src/cache/service/cache.options.service.ts @@ -0,0 +1,19 @@ +import { + CacheModuleOptions, + CacheOptionsFactory, + Injectable, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class CacheOptionsService implements CacheOptionsFactory { + private cacheConfig: Record; + constructor(private readonly configService: ConfigService) { + this.cacheConfig = + this.configService.get>('middleware.cache'); + } + + createCacheOptions(): CacheModuleOptions { + return this.cacheConfig; + } +} diff --git a/packages/auth-api/src/cache/service/cache.service.ts b/packages/auth-api/src/cache/service/cache.service.ts new file mode 100644 index 0000000..ccab54a --- /dev/null +++ b/packages/auth-api/src/cache/service/cache.service.ts @@ -0,0 +1,25 @@ +import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common'; +import { Cache } from 'cache-manager'; + +@Injectable() +export class CacheService { + constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: Cache) {} + async get(key: string): Promise { + return this.cacheManager.get(key); + } + async set(key: string, value: T): Promise { + return this.cacheManager.set(key, value); + } + + async setNoLimit(key: string, value: T): Promise { + return this.cacheManager.set(key, value, { ttl: 0 }); + } + + async delete(key: string): Promise { + return this.cacheManager.del(key); + } + + async reset(): Promise { + return this.cacheManager.reset(); + } +} diff --git a/packages/auth-api/src/cli.ts b/packages/auth-api/src/cli.ts new file mode 100644 index 0000000..2c597ce --- /dev/null +++ b/packages/auth-api/src/cli.ts @@ -0,0 +1,23 @@ +import { Logger } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; +import { CommandModule, CommandService } from 'nestjs-command'; +import { SeedsModule } from './database/seeds/seeds.module'; + +async function bootstrap() { + const app = await NestFactory.createApplicationContext(SeedsModule, { + logger: ['error'], + }); + + const logger = new Logger(); + + try { + await app.select(CommandModule).get(CommandService).exec(); + await app.close(); + } catch (error) { + logger.error(error, 'NestJsCommand'); + await app.close(); + process.exit(1); + } +} + +bootstrap(); diff --git a/packages/auth-api/src/config/app.config.ts b/packages/auth-api/src/config/app.config.ts new file mode 100644 index 0000000..fed1f32 --- /dev/null +++ b/packages/auth-api/src/config/app.config.ts @@ -0,0 +1,38 @@ +import { registerAs } from '@nestjs/config'; +import ms from 'ms'; + +export default registerAs( + 'app', + (): Record => ({ + name: process.env.APP_NAME || 'ack', + env: process.env.APP_ENV || 'development', + mode: process.env.APP_MODE || 'simple', + language: process.env.APP_LANGUAGE || 'en', + timezone: process.env.APP_TZ || 'Asia/Jakarta', + + http: { + host: process.env.APP_HOST || 'localhost', + port: Number.parseInt(process.env.APP_PORT) || 3000, + }, + globalPrefix: '/api', + versioning: { + on: process.env.APP_VERSIONING === 'true' || false, + prefix: 'v', + }, + debug: process.env.APP_DEBUG === 'true' || false, + debugger: { + http: { + maxFiles: 5, + maxSize: '2M', + }, + system: { + active: false, + maxFiles: ms('7d'), + maxSize: '2m', + }, + }, + + httpOn: process.env.APP_HTTP_ON === 'true' ? true : false, + taskOn: process.env.APP_TASK_ON === 'true' || false, + }) +); diff --git a/packages/auth-api/src/config/auth.config.ts b/packages/auth-api/src/config/auth.config.ts new file mode 100644 index 0000000..159e3fb --- /dev/null +++ b/packages/auth-api/src/config/auth.config.ts @@ -0,0 +1,45 @@ +import { registerAs } from '@nestjs/config'; +import ms from 'ms'; + +export default registerAs( + 'auth', + (): Record => ({ + jwt: { + accessToken: { + secretKey: + process.env.AUTH_JWT_ACCESS_TOKEN_SECRET_KEY || '123456', + expirationTime: process.env.AUTH_JWT_ACCESS_TOKEN_EXPIRED + ? ms(process.env.AUTH_JWT_ACCESS_TOKEN_EXPIRED) + : ms('30m'), // recommendation for production is 30m + notBeforeExpirationTime: ms(0), // keep it in zero value + }, + + refreshToken: { + secretKey: + process.env.AUTH_JWT_REFRESH_TOKEN_SECRET_KEY || + '123456000', + expirationTime: process.env.AUTH_JWT_REFRESH_TOKEN_EXPIRED + ? ms(process.env.AUTH_JWT_REFRESH_TOKEN_EXPIRED) + : ms('7d'), // recommendation for production is 7d + expirationTimeRememberMe: process.env + .AUTH_JWT_REFRESH_TOKEN_REMEMBER_ME_EXPIRED + ? ms(process.env.AUTH_JWT_REFRESH_TOKEN_REMEMBER_ME_EXPIRED) + : ms('30d'), // recommendation for production is 30d + notBeforeExpirationTime: process.env + .AUTH_JWT_ACCESS_TOKEN_EXPIRED + ? ms(process.env.AUTH_JWT_ACCESS_TOKEN_EXPIRED) + : ms('30m'), // recommendation for production is 30m + }, + }, + + password: { + saltLength: 8, + expiredInDay: 182, // recommendation for production is 182 days + }, + + basicToken: { + clientId: process.env.AUTH_BASIC_TOKEN_CLIENT_ID, + clientSecret: process.env.AUTH_BASIC_TOKEN_CLIENT_SECRET, + }, + }) +); diff --git a/packages/auth-api/src/config/aws.config.ts b/packages/auth-api/src/config/aws.config.ts new file mode 100644 index 0000000..d531b84 --- /dev/null +++ b/packages/auth-api/src/config/aws.config.ts @@ -0,0 +1,16 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs( + 'aws', + (): Record => ({ + credential: { + key: process.env.AWS_CREDENTIAL_KEY, + secret: process.env.AWS_CREDENTIAL_SECRET, + }, + s3: { + bucket: process.env.AWS_S3_BUCKET || 'acks3', + region: process.env.AWS_S3_REGION, + baseUrl: `https://${process.env.AWS_S3_BUCKET}.s3.${process.env.AWS_S3_REGION}.amazonaws.com`, + }, + }) +); diff --git a/packages/auth-api/src/config/database.config.ts b/packages/auth-api/src/config/database.config.ts new file mode 100644 index 0000000..1e2a97e --- /dev/null +++ b/packages/auth-api/src/config/database.config.ts @@ -0,0 +1,13 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs( + 'database', + (): Record => ({ + host: process.env.DATABASE_HOST || 'mongodb://localhost:27017', + name: process.env.DATABASE_NAME || 'ack', + user: process.env.DATABASE_USER || null, + password: process.env.DATABASE_PASSWORD || null, + debug: process.env.DATABASE_DEBUG === 'true' || false, + options: process.env.DATABASE_OPTIONS, + }) +); diff --git a/packages/auth-api/src/config/file.config.ts b/packages/auth-api/src/config/file.config.ts new file mode 100644 index 0000000..d14f442 --- /dev/null +++ b/packages/auth-api/src/config/file.config.ts @@ -0,0 +1,12 @@ +import { registerAs } from '@nestjs/config'; +import bytes from 'bytes'; + +export default registerAs( + 'file', + (): Record => ({ + fieldNameSize: bytes(100), // in bytes + fieldSize: bytes('500kb'), // 500 KB + maxFileSize: bytes('100kb'), // 100 KB + maxFiles: 2, // 2 files + }) +); diff --git a/packages/auth-api/src/config/helper.config.ts b/packages/auth-api/src/config/helper.config.ts new file mode 100644 index 0000000..862851d --- /dev/null +++ b/packages/auth-api/src/config/helper.config.ts @@ -0,0 +1,16 @@ +import { registerAs } from '@nestjs/config'; +import ms from 'ms'; + +export default registerAs( + 'helper', + (): Record => ({ + salt: { + length: 8, + }, + jwt: { + secretKey: '123456', + expirationTime: ms('1h'), + notBeforeExpirationTime: ms(0), + }, + }) +); diff --git a/packages/auth-api/src/config/index.ts b/packages/auth-api/src/config/index.ts new file mode 100644 index 0000000..b89c84b --- /dev/null +++ b/packages/auth-api/src/config/index.ts @@ -0,0 +1,19 @@ +import AppConfig from 'src/config/app.config'; +import AuthConfig from 'src/config/auth.config'; +import DatabaseConfig from 'src/config/database.config'; +import HelperConfig from 'src/config/helper.config'; +import AwsConfig from 'src/config/aws.config'; +import UserConfig from './user.config'; +import FileConfig from './file.config'; +import MiddlewareConfig from './middleware.config'; + +export default [ + AppConfig, + AuthConfig, + DatabaseConfig, + HelperConfig, + AwsConfig, + UserConfig, + MiddlewareConfig, + FileConfig, +]; diff --git a/packages/auth-api/src/config/middleware.config.ts b/packages/auth-api/src/config/middleware.config.ts new file mode 100644 index 0000000..a052bfc --- /dev/null +++ b/packages/auth-api/src/config/middleware.config.ts @@ -0,0 +1,65 @@ +import { registerAs } from '@nestjs/config'; +import ms from 'ms'; +import { ENUM_REQUEST_METHOD } from 'src/utils/request/request.constant'; + +export default registerAs( + 'middleware', + (): Record => ({ + cors: { + allowMethod: [ + ENUM_REQUEST_METHOD.GET, + ENUM_REQUEST_METHOD.DELETE, + ENUM_REQUEST_METHOD.PUT, + ENUM_REQUEST_METHOD.PATCH, + ENUM_REQUEST_METHOD.POST, + ], + allowOrigin: '*', + // allowOrigin: [/example\.com(\:\d{1,4})?$/], // allow all subdomain, and all port + // allowOrigin: [/example\.com$/], // allow only subdomain + allowHeader: [ + 'Accept', + 'Accept-Language', + 'Content-Language', + 'Content-Type', + 'Origin', + 'Authorization', + 'Access-Control-Request-Method', + 'Access-Control-Request-Headers', + 'Access-Control-Allow-Headers', + 'Access-Control-Allow-Origin', + 'Access-Control-Allow-Methods', + 'Access-Control-Allow-Credentials', + 'Access-Control-Expose-Headers', + 'Access-Control-Max-Age', + 'Referer', + 'Host', + 'X-Requested-With', + 'x-custom-lang', + 'x-timestamp', + 'x-api-key', + 'x-timezone', + 'x-request-id', + 'X-Response-Time', + 'user-agent', + ], + }, + rateLimit: { + resetTime: ms(500), // 0.5 secs + maxRequestPerId: 1, // max request per reset time + }, + timestamp: { + toleranceTimeInMinutes: process.env.MIDDLEWARE_TOLERANCE_TIMESTAMP + ? ms(process.env.MIDDLEWARE_TOLERANCE_TIMESTAMP) + : ms('5m'), // 5 mins + }, + cache: { + ttl: ms('30s'), // 30sec + max: 100, // maximum number of items in cache, + }, + timeout: { + in: process.env.MIDDLEWARE_TIMEOUT + ? ms(process.env.MIDDLEWARE_TIMEOUT) + : ms('30s'), // 30s based on ms module + }, + }) +); diff --git a/packages/auth-api/src/config/user.config.ts b/packages/auth-api/src/config/user.config.ts new file mode 100644 index 0000000..f5752de --- /dev/null +++ b/packages/auth-api/src/config/user.config.ts @@ -0,0 +1,8 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs( + 'user', + (): Record => ({ + uploadPath: '/user', + }) +); diff --git a/packages/auth-api/src/core/core.module.ts b/packages/auth-api/src/core/core.module.ts new file mode 100644 index 0000000..89d6552 --- /dev/null +++ b/packages/auth-api/src/core/core.module.ts @@ -0,0 +1,63 @@ +import { Module } from '@nestjs/common'; +import { MessageModule } from 'src/message/message.module'; +import { ConfigModule } from '@nestjs/config'; +import { WinstonModule } from 'nest-winston'; +import { DebuggerModule } from 'src/debugger/debugger.module'; +import Configs from 'src/config/index'; +import { AuthModule } from 'src/auth/auth.module'; +import { PaginationModule } from 'src/pagination/pagination.module'; +import { HelperModule } from 'src/utils/helper/helper.module'; +import { MiddlewareModule } from 'src/utils/middleware/middleware.module'; +import { DebuggerOptionService } from 'src/debugger/service/debugger.option.service'; +import { MongooseModule } from '@nestjs/mongoose'; +import { DATABASE_CONNECTION_NAME } from 'src/database/database.constant'; +import { DatabaseOptionsService } from 'src/database/service/database.options.service'; +import { LoggerModule } from 'src/logger/logger.module'; +import { RequestModule } from 'src/utils/request/request.module'; +import { ErrorModule } from 'src/utils/error/error.module'; +import { SettingModule } from 'src/setting/setting.module'; +import { VersionModule } from 'src/utils/version/version.module'; +import { ResponseModule } from 'src/utils/response/response.module'; +import { CacheModule } from 'src/cache/cache.module'; +import { DatabaseModule } from 'src/database/database.module'; + +@Module({ + controllers: [], + providers: [], + imports: [ + ConfigModule.forRoot({ + load: Configs, + ignoreEnvFile: false, + isGlobal: true, + cache: true, + envFilePath: ['.env'], + }), + WinstonModule.forRootAsync({ + inject: [DebuggerOptionService], + imports: [DebuggerModule], + useFactory: (debuggerOptionsService: DebuggerOptionService) => + debuggerOptionsService.createLogger(), + }), + MongooseModule.forRootAsync({ + connectionName: DATABASE_CONNECTION_NAME, + inject: [DatabaseOptionsService], + imports: [DatabaseModule], + useFactory: (databaseOptionsService: DatabaseOptionsService) => + databaseOptionsService.createMongooseOptions(), + }), + HelperModule, + DebuggerModule, + MessageModule, + ErrorModule, + ResponseModule, + PaginationModule, + SettingModule, + RequestModule, + VersionModule, + MiddlewareModule, + LoggerModule, + CacheModule, + AuthModule, + ], +}) +export class CoreModule {} diff --git a/packages/auth-api/src/database/database.constant.ts b/packages/auth-api/src/database/database.constant.ts new file mode 100644 index 0000000..d960dac --- /dev/null +++ b/packages/auth-api/src/database/database.constant.ts @@ -0,0 +1 @@ +export const DATABASE_CONNECTION_NAME = 'DatabaseConnectionName'; diff --git a/packages/auth-api/src/database/database.decorator.ts b/packages/auth-api/src/database/database.decorator.ts new file mode 100644 index 0000000..d7dca4a --- /dev/null +++ b/packages/auth-api/src/database/database.decorator.ts @@ -0,0 +1,15 @@ +import { InjectConnection, InjectModel } from '@nestjs/mongoose'; +import { DATABASE_CONNECTION_NAME } from './database.constant'; + +export function DatabaseConnection( + connectionName?: string +): (target: Record, key: string | symbol, index?: number) => void { + return InjectConnection(connectionName || DATABASE_CONNECTION_NAME); +} + +export function DatabaseEntity( + entity: string, + connectionName?: string +): (target: Record, key: string | symbol, index?: number) => void { + return InjectModel(entity, connectionName || DATABASE_CONNECTION_NAME); +} diff --git a/packages/auth-api/src/database/database.interface.ts b/packages/auth-api/src/database/database.interface.ts new file mode 100644 index 0000000..063b0f9 --- /dev/null +++ b/packages/auth-api/src/database/database.interface.ts @@ -0,0 +1,9 @@ +import { IPaginationOptions } from 'src/pagination/pagination.interface'; + +export interface IDatabaseFindOneOptions { + populate?: Record; +} + +export interface IDatabaseFindAllOptions + extends IPaginationOptions, + IDatabaseFindOneOptions {} diff --git a/packages/auth-api/src/database/database.module.ts b/packages/auth-api/src/database/database.module.ts new file mode 100644 index 0000000..2edd9a9 --- /dev/null +++ b/packages/auth-api/src/database/database.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { DatabaseOptionsService } from './service/database.options.service'; + +@Module({ + providers: [DatabaseOptionsService], + exports: [DatabaseOptionsService], + imports: [], +}) +export class DatabaseModule {} diff --git a/packages/auth-api/src/database/seeds/auth.api.seed.ts b/packages/auth-api/src/database/seeds/auth.api.seed.ts new file mode 100644 index 0000000..8fc8b18 --- /dev/null +++ b/packages/auth-api/src/database/seeds/auth.api.seed.ts @@ -0,0 +1,65 @@ +import { Command } from 'nestjs-command'; +import { Injectable } from '@nestjs/common'; +import { DebuggerService } from 'src/debugger/service/debugger.service'; +import { AuthApiService } from 'src/auth/service/auth.api.service'; +import { AuthApiBulkService } from 'src/auth/service/auth.api.bulk.service'; + +@Injectable() +export class AuthApiSeed { + constructor( + private readonly debuggerService: DebuggerService, + private readonly authApiService: AuthApiService, + private readonly authApiBulkService: AuthApiBulkService + ) {} + + @Command({ + command: 'insert:authapis', + describe: 'insert authapiss', + }) + async insert(): Promise { + try { + await this.authApiService.create({ + name: 'Auth Api Key Migration', + description: 'From migration', + key: 'qwertyuiop12345zxcvbnmkjh', + secret: '5124512412412asdasdasdasdasdASDASDASD', + passphrase: 'cuwakimacojulawu', + encryptionKey: 'opbUwdiS1FBsrDUoPgZdx', + }); + + this.debuggerService.debug(AuthApiSeed.name, { + description: 'Insert Auth Api Succeed', + class: 'AuthApiSeed', + function: 'insert', + }); + } catch (e) { + this.debuggerService.error(AuthApiSeed.name, { + description: e.message, + class: 'AuthApiSeed', + function: 'insert', + }); + } + } + + @Command({ + command: 'remove:authapis', + describe: 'remove authapis', + }) + async remove(): Promise { + try { + await this.authApiBulkService.deleteMany({}); + + this.debuggerService.debug(AuthApiSeed.name, { + description: 'Remove Auth Api Succeed', + class: 'AuthApiSeed', + function: 'remove', + }); + } catch (e) { + this.debuggerService.error(AuthApiSeed.name, { + description: e.message, + class: 'AuthApiSeed', + function: 'remove', + }); + } + } +} diff --git a/packages/auth-api/src/database/seeds/permission.seed.ts b/packages/auth-api/src/database/seeds/permission.seed.ts new file mode 100644 index 0000000..3b1797e --- /dev/null +++ b/packages/auth-api/src/database/seeds/permission.seed.ts @@ -0,0 +1,62 @@ +import { Command } from 'nestjs-command'; +import { Injectable } from '@nestjs/common'; +import { ENUM_PERMISSIONS } from 'src/permission/permission.constant'; +import { PermissionBulkService } from 'src/permission/service/permission.bulk.service'; +import { DebuggerService } from 'src/debugger/service/debugger.service'; + +@Injectable() +export class PermissionSeed { + constructor( + private readonly debuggerService: DebuggerService, + private readonly permissionBulkService: PermissionBulkService + ) {} + + @Command({ + command: 'insert:permission', + describe: 'insert permissions', + }) + async insert(): Promise { + try { + const permissions = Object.keys(ENUM_PERMISSIONS).map((val) => ({ + code: val, + name: val.replace('_', ' '), + })); + + await this.permissionBulkService.createMany(permissions); + + this.debuggerService.debug(PermissionSeed.name, { + description: 'Insert Permission Succeed', + class: 'PermissionSeed', + function: 'insert', + }); + } catch (e) { + this.debuggerService.error(PermissionSeed.name, { + description: e.message, + class: 'PermissionSeed', + function: 'insert', + }); + } + } + + @Command({ + command: 'remove:permission', + describe: 'remove permissions', + }) + async remove(): Promise { + try { + await this.permissionBulkService.deleteMany({}); + + this.debuggerService.debug(PermissionSeed.name, { + description: 'Remove Permission Succeed', + class: 'PermissionSeed', + function: 'remove', + }); + } catch (e) { + this.debuggerService.error(PermissionSeed.name, { + description: e.message, + class: 'PermissionSeed', + function: 'remove', + }); + } + } +} diff --git a/packages/auth-api/src/database/seeds/role.seed.ts b/packages/auth-api/src/database/seeds/role.seed.ts new file mode 100644 index 0000000..a5c7fbd --- /dev/null +++ b/packages/auth-api/src/database/seeds/role.seed.ts @@ -0,0 +1,77 @@ +import { Command } from 'nestjs-command'; +import { Injectable } from '@nestjs/common'; +import { ENUM_PERMISSIONS } from 'src/permission/permission.constant'; +import { PermissionService } from 'src/permission/service/permission.service'; +import { RoleBulkService } from 'src/role/service/role.bulk.service'; +import { DebuggerService } from 'src/debugger/service/debugger.service'; +import { PermissionDocument } from 'src/permission/schema/permission.schema'; + +@Injectable() +export class RoleSeed { + constructor( + private readonly debuggerService: DebuggerService, + private readonly permissionService: PermissionService, + private readonly roleBulkService: RoleBulkService + ) {} + + @Command({ + command: 'insert:role', + describe: 'insert roles', + }) + async insert(): Promise { + const permissions: PermissionDocument[] = + await this.permissionService.findAll({ + code: { $in: Object.values(ENUM_PERMISSIONS) }, + }); + + try { + const permissionsMap = permissions.map((val) => val._id); + await this.roleBulkService.createMany([ + { + name: 'admin', + permissions: permissionsMap, + isAdmin: true, + }, + { + name: 'user', + permissions: [], + isAdmin: false, + }, + ]); + + this.debuggerService.debug(RoleSeed.name, { + description: 'Insert Role Succeed', + class: 'RoleSeed', + function: 'insert', + }); + } catch (e) { + this.debuggerService.error(RoleSeed.name, { + description: e.message, + class: 'RoleSeed', + function: 'insert', + }); + } + } + + @Command({ + command: 'remove:role', + describe: 'remove roles', + }) + async remove(): Promise { + try { + await this.roleBulkService.deleteMany({}); + + this.debuggerService.debug(RoleSeed.name, { + description: 'Remove Role Succeed', + class: 'RoleSeed', + function: 'remove', + }); + } catch (e) { + this.debuggerService.error(RoleSeed.name, { + description: e.message, + class: 'RoleSeed', + function: 'remove', + }); + } + } +} diff --git a/packages/auth-api/src/database/seeds/seeds.module.ts b/packages/auth-api/src/database/seeds/seeds.module.ts new file mode 100644 index 0000000..b966e03 --- /dev/null +++ b/packages/auth-api/src/database/seeds/seeds.module.ts @@ -0,0 +1,26 @@ +import { Module } from '@nestjs/common'; +import { CommandModule } from 'nestjs-command'; +import { PermissionModule } from 'src/permission/permission.module'; +import { PermissionSeed } from 'src/database/seeds/permission.seed'; +import { RoleSeed } from './role.seed'; +import { RoleModule } from 'src/role/role.module'; +import { UserSeed } from './user.seed'; +import { UserModule } from 'src/user/user.module'; +import { AuthModule } from 'src/auth/auth.module'; +import { CoreModule } from 'src/core/core.module'; +import { AuthApiSeed } from './auth.api.seed'; +import { SettingSeed } from './setting.seed'; + +@Module({ + imports: [ + CoreModule, + CommandModule, + PermissionModule, + AuthModule, + UserModule, + RoleModule, + ], + providers: [AuthApiSeed, PermissionSeed, RoleSeed, UserSeed, SettingSeed], + exports: [], +}) +export class SeedsModule {} diff --git a/packages/auth-api/src/database/seeds/setting.seed.ts b/packages/auth-api/src/database/seeds/setting.seed.ts new file mode 100644 index 0000000..a48e1f0 --- /dev/null +++ b/packages/auth-api/src/database/seeds/setting.seed.ts @@ -0,0 +1,62 @@ +import { Command } from 'nestjs-command'; +import { Injectable } from '@nestjs/common'; +import { DebuggerService } from 'src/debugger/service/debugger.service'; +import { SettingService } from 'src/setting/service/setting.service'; +import { SettingBulkService } from 'src/setting/service/setting.bulk.service'; + +@Injectable() +export class SettingSeed { + constructor( + private readonly debuggerService: DebuggerService, + private readonly settingService: SettingService, + private readonly settingBulkService: SettingBulkService + ) {} + + @Command({ + command: 'insert:setting', + describe: 'insert settings', + }) + async insert(): Promise { + try { + await this.settingService.create({ + name: 'maintenance', + description: 'Maintenance Mode', + value: 'false', + }); + + this.debuggerService.debug(SettingSeed.name, { + description: 'Insert Setting Succeed', + class: 'SettingSeed', + function: 'insert', + }); + } catch (e) { + this.debuggerService.error(SettingSeed.name, { + description: e.message, + class: 'SettingSeed', + function: 'insert', + }); + } + } + + @Command({ + command: 'remove:setting', + describe: 'remove settings', + }) + async remove(): Promise { + try { + await this.settingBulkService.deleteMany({}); + + this.debuggerService.debug(SettingSeed.name, { + description: 'Remove Setting Succeed', + class: 'SettingSeed', + function: 'remove', + }); + } catch (e) { + this.debuggerService.error(SettingSeed.name, { + description: e.message, + class: 'SettingSeed', + function: 'remove', + }); + } + } +} diff --git a/packages/auth-api/src/database/seeds/user.seed.ts b/packages/auth-api/src/database/seeds/user.seed.ts new file mode 100644 index 0000000..e435536 --- /dev/null +++ b/packages/auth-api/src/database/seeds/user.seed.ts @@ -0,0 +1,82 @@ +import { Command } from 'nestjs-command'; +import { Injectable } from '@nestjs/common'; +import { UserService } from 'src/user/service/user.service'; +import { UserBulkService } from 'src/user/service/user.bulk.service'; +import { RoleService } from 'src/role/service/role.service'; +import { AuthService } from 'src/auth/service/auth.service'; +import { RoleDocument } from 'src/role/schema/role.schema'; +import { DebuggerService } from 'src/debugger/service/debugger.service'; + +@Injectable() +export class UserSeed { + constructor( + private readonly debuggerService: DebuggerService, + private readonly authService: AuthService, + private readonly userService: UserService, + private readonly userBulkService: UserBulkService, + private readonly roleService: RoleService + ) {} + + @Command({ + command: 'insert:user', + describe: 'insert users', + }) + async insert(): Promise { + const role: RoleDocument = await this.roleService.findOne( + { + name: 'admin', + } + ); + + try { + const password = await this.authService.createPassword( + 'aaAA@@123444' + ); + + await this.userService.create({ + firstName: 'admin', + lastName: 'test', + email: 'admin@mail.com', + password: password.passwordHash, + passwordExpired: password.passwordExpired, + mobileNumber: '08111111111', + role: role._id, + salt: password.salt, + }); + + this.debuggerService.debug(UserSeed.name, { + description: 'Insert User Succeed', + class: 'UserSeed', + function: 'insert', + }); + } catch (e) { + this.debuggerService.error(UserSeed.name, { + description: e.message, + class: 'UserSeed', + function: 'insert', + }); + } + } + + @Command({ + command: 'remove:user', + describe: 'remove users', + }) + async remove(): Promise { + try { + await this.userBulkService.deleteMany({}); + + this.debuggerService.debug(UserSeed.name, { + description: 'Remove User Succeed', + class: 'UserSeed', + function: 'remove', + }); + } catch (e) { + this.debuggerService.error(UserSeed.name, { + description: e.message, + class: 'UserSeed', + function: 'remove', + }); + } + } +} diff --git a/packages/auth-api/src/database/service/database.options.service.ts b/packages/auth-api/src/database/service/database.options.service.ts new file mode 100644 index 0000000..252fec7 --- /dev/null +++ b/packages/auth-api/src/database/service/database.options.service.ts @@ -0,0 +1,62 @@ +import { Injectable } from '@nestjs/common'; +import { + MongooseOptionsFactory, + MongooseModuleOptions, +} from '@nestjs/mongoose'; +import mongoose from 'mongoose'; +import { ConfigService } from '@nestjs/config'; +@Injectable() +export class DatabaseOptionsService implements MongooseOptionsFactory { + private readonly host: string; + private readonly database: string; + private readonly user: string; + private readonly password: string; + private readonly debug: boolean; + private readonly options: string; + private readonly env: string; + + constructor(private readonly configService: ConfigService) { + this.env = this.configService.get('app.env'); + this.host = this.configService.get('database.host'); + this.database = this.configService.get('database.name'); + this.user = this.configService.get('database.user'); + this.password = this.configService.get('database.password'); + this.debug = this.configService.get('database.debug'); + + /* istanbul ignore next */ + this.options = this.configService.get('database.options') + ? `?${this.configService.get('database.options')}` + : ''; + } + + createMongooseOptions(): MongooseModuleOptions { + let uri = `${this.host}`; + + if (this.database) { + uri = `${uri}/${this.database}${this.options}`; + } + + /* istanbul ignore next */ + if (this.env !== 'production') { + mongoose.set('debug', this.debug); + } + + const mongooseOptions: MongooseModuleOptions = { + uri, + useNewUrlParser: true, + useUnifiedTopology: true, + serverSelectionTimeoutMS: 5000, + // useMongoClient: true + }; + + /* istanbul ignore next */ + if (this.user && this.password) { + mongooseOptions.auth = { + username: this.user, + password: this.password, + }; + } + + return mongooseOptions; + } +} diff --git a/packages/auth-api/src/debugger/debugger.constant.ts b/packages/auth-api/src/debugger/debugger.constant.ts new file mode 100644 index 0000000..568afcf --- /dev/null +++ b/packages/auth-api/src/debugger/debugger.constant.ts @@ -0,0 +1 @@ +export const DEBUGGER_NAME = 'system'; diff --git a/packages/auth-api/src/debugger/debugger.interface.ts b/packages/auth-api/src/debugger/debugger.interface.ts new file mode 100644 index 0000000..73f8aa7 --- /dev/null +++ b/packages/auth-api/src/debugger/debugger.interface.ts @@ -0,0 +1,5 @@ +export interface IDebuggerLog { + description: string; + class?: string; + function?: string; +} diff --git a/packages/auth-api/src/debugger/debugger.module.ts b/packages/auth-api/src/debugger/debugger.module.ts new file mode 100644 index 0000000..7293a08 --- /dev/null +++ b/packages/auth-api/src/debugger/debugger.module.ts @@ -0,0 +1,11 @@ +import { Global, Module } from '@nestjs/common'; +import { DebuggerOptionService } from './service/debugger.option.service'; +import { DebuggerService } from './service/debugger.service'; + +@Global() +@Module({ + providers: [DebuggerOptionService, DebuggerService], + exports: [DebuggerOptionService, DebuggerService], + imports: [], +}) +export class DebuggerModule {} diff --git a/packages/auth-api/src/debugger/service/debugger.option.service.ts b/packages/auth-api/src/debugger/service/debugger.option.service.ts new file mode 100644 index 0000000..539b88f --- /dev/null +++ b/packages/auth-api/src/debugger/service/debugger.option.service.ts @@ -0,0 +1,81 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { LoggerOptions } from 'winston'; +import winston from 'winston'; +import { DEBUGGER_NAME } from '../debugger.constant'; +import DailyRotateFile from 'winston-daily-rotate-file'; + +@Injectable() +export class DebuggerOptionService { + private readonly env: string; + private readonly debug: boolean; + private readonly logger: boolean; + private readonly maxSize: string; + private readonly maxFiles: string; + + constructor(private configService: ConfigService) { + this.env = this.configService.get('app.env'); + this.debug = this.configService.get('app.debug'); + this.logger = this.configService.get( + 'app.debugger.system.active' + ); + this.maxSize = this.configService.get( + 'app.debugger.system.maxSize' + ); + this.maxFiles = this.configService.get( + 'app.debugger.system.maxFiles' + ); + } + + createLogger(): LoggerOptions { + const transports = []; + + transports.push( + new DailyRotateFile({ + filename: `%DATE%.log`, + dirname: `logs/${DEBUGGER_NAME}/error`, + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: this.maxSize, + maxFiles: this.maxFiles, + level: 'error', + }) + ); + transports.push( + new DailyRotateFile({ + filename: `%DATE%.log`, + dirname: `logs/${DEBUGGER_NAME}/default`, + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: this.maxSize, + maxFiles: this.maxFiles, + level: 'info', + }) + ); + transports.push( + new DailyRotateFile({ + filename: `%DATE%.log`, + dirname: `logs/${DEBUGGER_NAME}/debug`, + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: this.maxSize, + maxFiles: this.maxFiles, + level: 'debug', + }) + ); + + /* istanbul ignore next */ + if ((this.debug || this.logger) && this.env !== 'production') { + transports.push(new winston.transports.Console()); + } + + const loggerOptions: LoggerOptions = { + format: winston.format.combine( + winston.format.timestamp(), + winston.format.prettyPrint() + ), + transports, + }; + return loggerOptions; + } +} diff --git a/packages/auth-api/src/debugger/service/debugger.service.ts b/packages/auth-api/src/debugger/service/debugger.service.ts new file mode 100644 index 0000000..2455d00 --- /dev/null +++ b/packages/auth-api/src/debugger/service/debugger.service.ts @@ -0,0 +1,38 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Logger } from 'winston'; +import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; +import { IDebuggerLog } from '../debugger.interface'; + +@Injectable() +export class DebuggerService { + constructor( + @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger + ) {} + + info(_id: string, log: IDebuggerLog, data?: any): void { + this.logger.info(log.description, { + _id, + class: log.class, + function: log.function, + data, + }); + } + + debug(_id: string, log: IDebuggerLog, data?: any): void { + this.logger.debug(log.description, { + _id, + class: log.class, + function: log.function, + data, + }); + } + + error(_id: string, log: IDebuggerLog, data?: any): void { + this.logger.error(log.description, { + _id, + class: log.class, + function: log.function, + data, + }); + } +} diff --git a/packages/auth-api/src/health/controller/health.common.controller.ts b/packages/auth-api/src/health/controller/health.common.controller.ts new file mode 100644 index 0000000..63d95e5 --- /dev/null +++ b/packages/auth-api/src/health/controller/health.common.controller.ts @@ -0,0 +1,135 @@ +import { + Controller, + Get, + InternalServerErrorException, + VERSION_NEUTRAL, +} from '@nestjs/common'; +import { + DiskHealthIndicator, + HealthCheck, + HealthCheckService, + MemoryHealthIndicator, + MongooseHealthIndicator, +} from '@nestjs/terminus'; +import { Connection } from 'mongoose'; +import { DatabaseConnection } from 'src/database/database.decorator'; +import { AwsHealthIndicator } from '../indicator/health.aws.indicator'; +import { IResponse } from 'src/utils/response/response.interface'; +import { Response } from 'src/utils/response/response.decorator'; +import { ENUM_STATUS_CODE_ERROR } from 'src/utils/error/error.constant'; +import { ErrorMeta } from 'src/utils/error/error.decorator'; + +@Controller({ + version: VERSION_NEUTRAL, + path: 'health', +}) +export class HealthCommonController { + constructor( + @DatabaseConnection() private readonly databaseConnection: Connection, + private readonly health: HealthCheckService, + private readonly memoryHealthIndicator: MemoryHealthIndicator, + private readonly diskHealthIndicator: DiskHealthIndicator, + private readonly databaseIndicator: MongooseHealthIndicator, + private readonly awsIndicator: AwsHealthIndicator + ) {} + + @Response('health.check') + @HealthCheck() + @ErrorMeta(HealthCommonController.name, 'aws') + @Get('/aws') + async checkAws(): Promise { + try { + return this.health.check([ + () => this.awsIndicator.isHealthy('awsBucket'), + ]); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + } + + @Response('health.check') + @HealthCheck() + @ErrorMeta(HealthCommonController.name, 'checkDatabase') + @Get('/database') + async checkDatabase(): Promise { + try { + return this.health.check([ + () => + this.databaseIndicator.pingCheck('database', { + connection: this.databaseConnection, + }), + ]); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + } + + @Response('health.check') + @HealthCheck() + @ErrorMeta(HealthCommonController.name, 'checkMemoryHeap') + @Get('/memory-heap') + async checkMemoryHeap(): Promise { + try { + return this.health.check([ + () => + this.memoryHealthIndicator.checkHeap( + 'memoryHeap', + 300 * 1024 * 1024 + ), + ]); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + } + + @Response('health.check') + @HealthCheck() + @ErrorMeta(HealthCommonController.name, 'checkMemoryRss') + @Get('/memory-rss') + async checkMemoryRss(): Promise { + try { + return this.health.check([ + () => + this.memoryHealthIndicator.checkRSS( + 'memoryRss', + 300 * 1024 * 1024 + ), + ]); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + } + + @Response('health.check') + @HealthCheck() + @ErrorMeta(HealthCommonController.name, 'checkStorage') + @Get('/storage') + async checkStorage(): Promise { + try { + return this.health.check([ + () => + this.diskHealthIndicator.checkStorage('diskHealth', { + thresholdPercent: 0.75, + path: '/', + }), + ]); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + } +} diff --git a/packages/auth-api/src/health/health.module.ts b/packages/auth-api/src/health/health.module.ts new file mode 100644 index 0000000..817867b --- /dev/null +++ b/packages/auth-api/src/health/health.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { AwsModule } from 'src/aws/aws.module'; +import { AwsHealthIndicator } from './indicator/health.aws.indicator'; + +@Module({ + providers: [AwsHealthIndicator], + exports: [AwsHealthIndicator], + imports: [AwsModule], +}) +export class HealthModule {} diff --git a/packages/auth-api/src/health/indicator/health.aws.indicator.ts b/packages/auth-api/src/health/indicator/health.aws.indicator.ts new file mode 100644 index 0000000..e27d2dc --- /dev/null +++ b/packages/auth-api/src/health/indicator/health.aws.indicator.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { + HealthCheckError, + HealthIndicator, + HealthIndicatorResult, +} from '@nestjs/terminus'; +import { AwsS3Service } from 'src/aws/service/aws.s3.service'; + +@Injectable() +export class AwsHealthIndicator extends HealthIndicator { + constructor(private readonly awsS3Service: AwsS3Service) { + super(); + } + + async isHealthy(key: string): Promise { + try { + await this.awsS3Service.listBucket(); + return this.getStatus(key, true); + } catch (error) { + throw new HealthCheckError( + 'ElasticsearchHealthIndicator failed', + this.getStatus(key, false) + ); + } + } +} diff --git a/packages/auth-api/src/logger/interceptor/logger.interceptor.ts b/packages/auth-api/src/logger/interceptor/logger.interceptor.ts new file mode 100644 index 0000000..b7c12ae --- /dev/null +++ b/packages/auth-api/src/logger/interceptor/logger.interceptor.ts @@ -0,0 +1,113 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, + mixin, + Type, +} from '@nestjs/common'; +import { Observable, tap } from 'rxjs'; +import { LoggerService } from '../service/logger.service'; +import { ILoggerOptions } from '../logger.interface'; +import { ENUM_LOGGER_ACTION, ENUM_LOGGER_LEVEL } from '../logger.constant'; +import { IRequestApp } from 'src/utils/request/request.interface'; +import { ENUM_REQUEST_METHOD } from 'src/utils/request/request.constant'; + +export function LoggerInterceptor( + action: ENUM_LOGGER_ACTION, + options?: ILoggerOptions +): Type { + @Injectable() + class MixinLoggerInterceptor implements NestInterceptor> { + constructor(private readonly loggerService: LoggerService) {} + + async intercept( + context: ExecutionContext, + next: CallHandler + ): Promise | string>> { + if (context.getType() === 'http') { + const { apiKey, method, originalUrl, user, id } = context + .switchToHttp() + .getRequest(); + return next.handle().pipe( + tap(async () => { + if ( + options && + options.level === ENUM_LOGGER_LEVEL.FATAL + ) { + await this.loggerService.fatal({ + action, + description: + options && options.description + ? options.description + : `Request ${method} called, url ${originalUrl}, and action ${action}`, + apiKey: apiKey ? apiKey._id : undefined, + user: user ? user._id : undefined, + requestId: id, + method: method as ENUM_REQUEST_METHOD, + role: user ? user.role : undefined, + tags: + options && options.tags ? options.tags : [], + }); + } else if ( + options && + options.level === ENUM_LOGGER_LEVEL.DEBUG + ) { + await this.loggerService.debug({ + action, + description: + options && options.description + ? options.description + : `Request ${method} called, url ${originalUrl}, and action ${action}`, + apiKey: apiKey ? apiKey._id : undefined, + user: user ? user._id : undefined, + requestId: id, + method: method as ENUM_REQUEST_METHOD, + role: user ? user.role : undefined, + tags: + options && options.tags ? options.tags : [], + }); + } else if ( + options && + options.level === ENUM_LOGGER_LEVEL.WARM + ) { + await this.loggerService.warning({ + action, + description: + options && options.description + ? options.description + : `Request ${method} called, url ${originalUrl}, and action ${action}`, + apiKey: apiKey ? apiKey._id : undefined, + user: user ? user._id : undefined, + requestId: id, + method: method as ENUM_REQUEST_METHOD, + role: user ? user.role : undefined, + tags: + options && options.tags ? options.tags : [], + }); + } else { + await this.loggerService.info({ + action, + description: + options && options.description + ? options.description + : `Request ${method} called, url ${originalUrl}, and action ${action}`, + apiKey: apiKey ? apiKey._id : undefined, + user: user ? user._id : undefined, + requestId: id, + method: method as ENUM_REQUEST_METHOD, + role: user ? user.role : undefined, + tags: + options && options.tags ? options.tags : [], + }); + } + }) + ); + } + + return next.handle(); + } + } + + return mixin(MixinLoggerInterceptor); +} diff --git a/packages/auth-api/src/logger/logger.constant.ts b/packages/auth-api/src/logger/logger.constant.ts new file mode 100644 index 0000000..6ff677a --- /dev/null +++ b/packages/auth-api/src/logger/logger.constant.ts @@ -0,0 +1,11 @@ +export enum ENUM_LOGGER_LEVEL { + DEBUG = 'DEBUG', + INFO = 'INFO', + WARM = 'WARM', + FATAL = 'FATAL', +} + +export enum ENUM_LOGGER_ACTION { + LOGIN = 'LOGIN', + TEST = 'TEST', +} diff --git a/packages/auth-api/src/logger/logger.decorator.ts b/packages/auth-api/src/logger/logger.decorator.ts new file mode 100644 index 0000000..fc48788 --- /dev/null +++ b/packages/auth-api/src/logger/logger.decorator.ts @@ -0,0 +1,11 @@ +import { applyDecorators, UseInterceptors } from '@nestjs/common'; +import { LoggerInterceptor } from './interceptor/logger.interceptor'; +import { ENUM_LOGGER_ACTION } from './logger.constant'; +import { ILoggerOptions } from './logger.interface'; + +export function Logger( + action: ENUM_LOGGER_ACTION, + options?: ILoggerOptions +): any { + return applyDecorators(UseInterceptors(LoggerInterceptor(action, options))); +} diff --git a/packages/auth-api/src/logger/logger.interface.ts b/packages/auth-api/src/logger/logger.interface.ts new file mode 100644 index 0000000..a3b9a77 --- /dev/null +++ b/packages/auth-api/src/logger/logger.interface.ts @@ -0,0 +1,24 @@ +import { ENUM_REQUEST_METHOD } from 'src/utils/request/request.constant'; +import { ENUM_LOGGER_ACTION, ENUM_LOGGER_LEVEL } from './logger.constant'; + +export interface ILoggerRole { + _id: string; + isAdmin: boolean; +} + +export interface ILogger { + action: ENUM_LOGGER_ACTION; + description: string; + apiKey?: string; + user?: string; + requestId?: string; + method: ENUM_REQUEST_METHOD; + role?: ILoggerRole; + tags?: string[]; +} + +export interface ILoggerOptions { + description?: string; + tags?: string[]; + level?: ENUM_LOGGER_LEVEL; +} diff --git a/packages/auth-api/src/logger/logger.module.ts b/packages/auth-api/src/logger/logger.module.ts new file mode 100644 index 0000000..f4272e1 --- /dev/null +++ b/packages/auth-api/src/logger/logger.module.ts @@ -0,0 +1,28 @@ +import { Global, Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { DATABASE_CONNECTION_NAME } from 'src/database/database.constant'; +import { + LoggerDatabaseName, + LoggerEntity, + LoggerSchema, +} from './schema/logger.schema'; +import { LoggerService } from './service/logger.service'; + +@Global() +@Module({ + providers: [LoggerService], + exports: [LoggerService], + imports: [ + MongooseModule.forFeature( + [ + { + name: LoggerEntity.name, + schema: LoggerSchema, + collection: LoggerDatabaseName, + }, + ], + DATABASE_CONNECTION_NAME + ), + ], +}) +export class LoggerModule {} diff --git a/packages/auth-api/src/logger/schema/logger.schema.ts b/packages/auth-api/src/logger/schema/logger.schema.ts new file mode 100644 index 0000000..20b9e28 --- /dev/null +++ b/packages/auth-api/src/logger/schema/logger.schema.ts @@ -0,0 +1,79 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Types } from 'mongoose'; +import { AuthApiEntity } from 'src/auth/schema/auth.api.schema'; +import { RoleEntity } from 'src/role/schema/role.schema'; +import { UserEntity } from 'src/user/schema/user.schema'; +import { ENUM_REQUEST_METHOD } from 'src/utils/request/request.constant'; +import { ENUM_LOGGER_ACTION, ENUM_LOGGER_LEVEL } from '../logger.constant'; + +@Schema({ timestamps: true, versionKey: false }) +export class LoggerEntity { + @Prop({ + required: true, + enum: ENUM_LOGGER_LEVEL, + }) + level: string; + + @Prop({ + required: true, + enum: ENUM_LOGGER_ACTION, + }) + action: string; + + @Prop({ + required: true, + enum: ENUM_REQUEST_METHOD, + }) + method: string; + + @Prop({ + required: false, + }) + requestId?: string; + + @Prop({ + required: false, + ref: UserEntity.name, + }) + user?: Types.ObjectId; + + @Prop({ + required: false, + ref: RoleEntity.name, + }) + role?: Types.ObjectId; + + @Prop({ + required: false, + ref: AuthApiEntity.name, + }) + apiKey?: Types.ObjectId; + + @Prop({ + required: true, + default: true, + }) + anonymous: boolean; + + @Prop({ + required: true, + default: true, + }) + isPublic: boolean; + + @Prop({ + required: true, + }) + description: string; + + @Prop({ + required: false, + default: [], + }) + tags: string[]; +} + +export const LoggerDatabaseName = 'loggers'; +export const LoggerSchema = SchemaFactory.createForClass(LoggerEntity); + +export type LoggerDocument = LoggerEntity & Document; diff --git a/packages/auth-api/src/logger/service/logger.service.ts b/packages/auth-api/src/logger/service/logger.service.ts new file mode 100644 index 0000000..7b530d1 --- /dev/null +++ b/packages/auth-api/src/logger/service/logger.service.ts @@ -0,0 +1,118 @@ +import { Injectable } from '@nestjs/common'; +import { Model, Types } from 'mongoose'; +import { DatabaseEntity } from 'src/database/database.decorator'; +import { ILogger } from '../logger.interface'; +import { ENUM_LOGGER_LEVEL } from '../logger.constant'; +import { LoggerDocument, LoggerEntity } from '../schema/logger.schema'; + +@Injectable() +export class LoggerService { + constructor( + @DatabaseEntity(LoggerEntity.name) + private readonly loggerModel: Model + ) {} + + async info({ + action, + description, + apiKey, + user, + method, + requestId, + role, + tags, + }: ILogger): Promise { + const create = new this.loggerModel({ + level: ENUM_LOGGER_LEVEL.INFO, + user: user ? new Types.ObjectId(user) : undefined, + apiKey: apiKey ? new Types.ObjectId(apiKey) : undefined, + anonymous: user ? false : true, + action, + description, + method, + requestId, + role: role ? new Types.ObjectId(role._id) : undefined, + isPublic: role && role.isAdmin ? false : true, + tags, + }); + return create.save(); + } + + async debug({ + action, + description, + apiKey, + user, + method, + requestId, + role, + tags, + }: ILogger): Promise { + const create = new this.loggerModel({ + level: ENUM_LOGGER_LEVEL.DEBUG, + user: user ? new Types.ObjectId(user) : undefined, + apiKey: apiKey ? new Types.ObjectId(apiKey) : undefined, + anonymous: user ? false : true, + action, + description, + method, + requestId, + role: role ? new Types.ObjectId(role._id) : undefined, + isPublic: role && role.isAdmin ? false : true, + tags, + }); + return create.save(); + } + + async warning({ + action, + description, + apiKey, + user, + method, + requestId, + role, + tags, + }: ILogger): Promise { + const create = new this.loggerModel({ + level: ENUM_LOGGER_LEVEL.WARM, + user: user ? new Types.ObjectId(user) : undefined, + apiKey: apiKey ? new Types.ObjectId(apiKey) : undefined, + anonymous: user ? false : true, + action, + description, + method, + requestId, + role: role ? new Types.ObjectId(role._id) : undefined, + isPublic: role && role.isAdmin ? false : true, + tags, + }); + return create.save(); + } + + async fatal({ + action, + description, + apiKey, + user, + method, + requestId, + role, + tags, + }: ILogger): Promise { + const create = new this.loggerModel({ + level: ENUM_LOGGER_LEVEL.FATAL, + user: user ? new Types.ObjectId(user) : undefined, + apiKey: apiKey ? new Types.ObjectId(apiKey) : undefined, + anonymous: user ? false : true, + action, + description, + method, + requestId, + role: role ? new Types.ObjectId(role._id) : undefined, + isPublic: role && role.isAdmin ? false : true, + tags, + }); + return create.save(); + } +} diff --git a/packages/auth-api/src/main.ts b/packages/auth-api/src/main.ts new file mode 100644 index 0000000..2856f56 --- /dev/null +++ b/packages/auth-api/src/main.ts @@ -0,0 +1,77 @@ +import { NestApplication, NestFactory } from '@nestjs/core'; +import { Logger, VersioningType, VERSION_NEUTRAL } from '@nestjs/common'; +import { AppModule } from 'src/app/app.module'; +import { ConfigService } from '@nestjs/config'; +import { useContainer } from 'class-validator'; + +async function bootstrap() { + const app: NestApplication = await NestFactory.create(AppModule); + const configService = app.get(ConfigService); + const env: string = configService.get('app.env'); + const tz: string = configService.get('app.timezone'); + const host: string = configService.get('app.http.host'); + const port: number = configService.get('app.http.port'); + const globalPrefix: string = configService.get('app.globalPrefix'); + const versioning: boolean = configService.get('app.versioning.on'); + const versioningPrefix: string = configService.get( + 'app.versioning.prefix' + ); + + const logger = new Logger(); + process.env.TZ = tz; + process.env.NODE_ENV = env; + + // Global + app.setGlobalPrefix(globalPrefix); + useContainer(app.select(AppModule), { fallbackOnErrors: true }); + + // Versioning + if (versioning) { + app.enableVersioning({ + type: VersioningType.URI, + defaultVersion: VERSION_NEUTRAL, + prefix: versioningPrefix, + }); + } + + // Listen + await app.listen(port, host); + + logger.log(`==========================================================`); + logger.log(`App Environment is ${env}`, 'NestApplication'); + logger.log( + `App Language is ${configService.get('app.language')}`, + 'NestApplication' + ); + logger.log( + `App Debug is ${configService.get('app.debug')}`, + 'NestApplication' + ); + logger.log(`App Versioning is ${versioning}`, 'NestApplication'); + logger.log( + `App Http is ${configService.get('app.httpOn')}`, + 'NestApplication' + ); + logger.log( + `App Task is ${configService.get('app.taskOn')}`, + 'NestApplication' + ); + logger.log(`App Timezone is ${tz}`, 'NestApplication'); + logger.log( + `Database Debug is ${configService.get('database.debug')}`, + 'NestApplication' + ); + + logger.log(`==========================================================`); + + logger.log( + `Database running on ${configService.get( + 'database.host' + )}/${configService.get('database.name')}`, + 'NestApplication' + ); + logger.log(`Server running on ${await app.getUrl()}`, 'NestApplication'); + + logger.log(`==========================================================`); +} +bootstrap(); diff --git a/packages/auth-api/src/message/controller/message.enum.controller.ts b/packages/auth-api/src/message/controller/message.enum.controller.ts new file mode 100644 index 0000000..68b68f5 --- /dev/null +++ b/packages/auth-api/src/message/controller/message.enum.controller.ts @@ -0,0 +1,23 @@ +import { Controller, Get } from '@nestjs/common'; +import { ErrorMeta } from 'src/utils/error/error.decorator'; +import { Response } from 'src/utils/response/response.decorator'; +import { IResponse } from 'src/utils/response/response.interface'; +import { MessageService } from '../service/message.service'; + +@Controller({ + version: '1', + path: 'message', +}) +export class MessageEnumController { + constructor(private readonly messageService: MessageService) {} + + @Response('message.enum.languages') + @ErrorMeta(MessageEnumController.name, 'languages') + @Get('/languages') + async languages(): Promise { + const languages: string[] = await this.messageService.getLanguages(); + return { + languages, + }; + } +} diff --git a/packages/auth-api/src/message/languages/en/auth.json b/packages/auth-api/src/message/languages/en/auth.json new file mode 100644 index 0000000..ad3cb57 --- /dev/null +++ b/packages/auth-api/src/message/languages/en/auth.json @@ -0,0 +1,28 @@ +{ + "login": "Login success.", + "refresh": "Refresh token success", + "signUp": "Sign up Success", + "changePassword": "Change Password Success", + "error": { + "roleBlocked": "Role blocked", + "blocked": "Blocked", + "passwordExpired": + "Password expired, you need to change your password asap", + "admin": "Admin invalid", + "passwordNotMatch": "Password not match", + "newPasswordMustDifference": + "Your new password can't be same with previous password" + }, + "apiKey": { + "error": { + "keyNeeded": "Need API Key", + "prefixInvalid": "Prefix API Key invalid", + "schemaInvalid": "API Key Schema invalid", + "timestampNotMatchWithRequest": "Timestamp not match with request", + "notFound": "Auth API not found", + "inactive": "Auth API Inactive", + "invalid": "Invalid API Key" + } + } +} + diff --git a/packages/auth-api/src/message/languages/en/file.json b/packages/auth-api/src/message/languages/en/file.json new file mode 100644 index 0000000..712e366 --- /dev/null +++ b/packages/auth-api/src/message/languages/en/file.json @@ -0,0 +1,8 @@ +{ + "error": { + "notFound": "File not found", + "maxSize": "File size too big", + "maxFiles": "Files are to many", + "mimeInvalid": "File extension not valid" + } +} diff --git a/packages/auth-api/src/message/languages/en/health.json b/packages/auth-api/src/message/languages/en/health.json new file mode 100644 index 0000000..ed211d6 --- /dev/null +++ b/packages/auth-api/src/message/languages/en/health.json @@ -0,0 +1,6 @@ +{ + "check": "Healthy", + "error": { + "check": "Unhealthy" + } +} \ No newline at end of file diff --git a/packages/auth-api/src/message/languages/en/http.json b/packages/auth-api/src/message/languages/en/http.json new file mode 100644 index 0000000..ad2a7d4 --- /dev/null +++ b/packages/auth-api/src/message/languages/en/http.json @@ -0,0 +1,38 @@ +{ + "success": { + "ok": "OK", + "created": "Created", + "accepted": "Accepted", + "noContent": "No Content" + }, + "redirection": { + "movePermanently": "Move Permanently", + "found": "Found", + "notModified": "Not Modified", + "temporaryRedirect": "Temporary Redirect", + "permanentRedirect": "Permanent Redirect" + }, + "clientError": { + "badRequest": "Bad Request", + "unauthorized": "Unauthorized", + "forbidden": "Forbidden", + "notFound": "Not Found", + "methodNotAllowed": "Not Allowed Method", + "notAcceptable": "Not Acceptable", + "requestTimeOut": "Request time out", + "payloadToLarge": "Payload To Large", + "uriToLarge": "Uri To Large", + "unSupportMediaType": "Not Found", + "unprocessableEntity": "Unprocessable Entity", + "tooManyRequest": "Too Many Request", + "requestHeaderFieldsTooLarge": "Request Header Fields Too Large", + "unavailableForLegalReasons": "Unavailable For Legal Reasons" + }, + "serverError": { + "internalServerError": "Internal Server Error", + "notImplemented": "Not Implemented", + "badGateway": "Bad Gateway", + "serviceUnavailable": "Service Unavailable", + "gatewayTimeout": "Gateway Timeout" + } +} diff --git a/packages/auth-api/src/message/languages/en/message.json b/packages/auth-api/src/message/languages/en/message.json new file mode 100644 index 0000000..8083371 --- /dev/null +++ b/packages/auth-api/src/message/languages/en/message.json @@ -0,0 +1,5 @@ +{ + "enum": { + "languages": "message enum languages" + } +} \ No newline at end of file diff --git a/packages/auth-api/src/message/languages/en/middleware.json b/packages/auth-api/src/message/languages/en/middleware.json new file mode 100644 index 0000000..23cead8 --- /dev/null +++ b/packages/auth-api/src/message/languages/en/middleware.json @@ -0,0 +1,6 @@ +{ + "error": { + "timestampInvalid": "Request timestamp not acceptable", + "userAgentInvalid": "Request user agent not acceptable" + } +} \ No newline at end of file diff --git a/packages/auth-api/src/message/languages/en/permission.json b/packages/auth-api/src/message/languages/en/permission.json new file mode 100644 index 0000000..0f26df4 --- /dev/null +++ b/packages/auth-api/src/message/languages/en/permission.json @@ -0,0 +1,12 @@ +{ + "list": "Permission list succeed", + "get": "Permission get succeed", + "update": "Permission update succeed", + "active": "Permission active succeed", + "inactive": "Permission inactive succeed", + "error": { + "notFound": "Permission not found", + "forbidden": "Permission invalid", + "active": "Permission active status invalid" + } +} diff --git a/packages/auth-api/src/message/languages/en/request.json b/packages/auth-api/src/message/languages/en/request.json new file mode 100644 index 0000000..8f3bfd5 --- /dev/null +++ b/packages/auth-api/src/message/languages/en/request.json @@ -0,0 +1,30 @@ +{ + "default": "Validation errors", + "min": "{property} has less elements than the minimum allowed.", + "max": "{property} has more elements than the maximum allowed.", + "maxLength": "{property} has more elements than the maximum allowed.", + "minLength": "{property} has less elements than the minimum allowed.", + "isString": "{property} should be a type of string.", + "isNotEmpty": "{property} cannot be empty.", + "isLowercase": "{property} should be lowercase.", + "isOptional": "{property} is optional.", + "isPositive": "{property} should be a positive number.", + "isEmail": "{property} should be a type of email.", + "isInt": "{property} should be a number.", + "isNumberString": "{property} should be a number.", + "isNumber": "{property} should be a number {value}.", + "isMongoId": "{property} should reference with mongo object id.", + "isBoolean": "{property} should be a boolean", + "IsStartWith": "{property} should start with {value}", + "isEnum": "{property} don't match with enum", + "isObject": "{property} should be a object", + "isArray": "{property} should be a array", + "arrayNotEmpty": "{property} array is not empty", + "minDate": "{property} has less date than the minimum allowed.", + "maxDate": "{property} has more elements than the maximum allowed.", + "isDate": "{property} should be a date", + "minDateGreaterThan": "{property} has less date than the {value}", + "isPasswordStrong": "{property} must have strong pattern", + "isPasswordMedium": "{property} must have medium pattern", + "isPasswordWeak": "{property} must have weak pattern" +} diff --git a/packages/auth-api/src/message/languages/en/response.json b/packages/auth-api/src/message/languages/en/response.json new file mode 100644 index 0000000..f599cb7 --- /dev/null +++ b/packages/auth-api/src/message/languages/en/response.json @@ -0,0 +1,6 @@ +{ + "default": "Response message default", + "error": { + "structure": "Error response structure invalid" + } +} diff --git a/packages/auth-api/src/message/languages/en/role.json b/packages/auth-api/src/message/languages/en/role.json new file mode 100644 index 0000000..446cbb1 --- /dev/null +++ b/packages/auth-api/src/message/languages/en/role.json @@ -0,0 +1,16 @@ +{ + "list": "List Role Success.", + "get": "Get Role Success.", + "create": "Create Succeed", + "delete": "Delete Succeed", + "update": "Update Succeed", + "inactive": "Inactive Succeed", + "active": "Active Succeed", + "error": { + "notFound": "Role not found", + "inactive": "Role inactive", + "exist": "Role exist", + "active": "Role status active invalid", + "used": "Role in used" + } +} diff --git a/packages/auth-api/src/message/languages/en/setting.json b/packages/auth-api/src/message/languages/en/setting.json new file mode 100644 index 0000000..59b2dc9 --- /dev/null +++ b/packages/auth-api/src/message/languages/en/setting.json @@ -0,0 +1,9 @@ +{ + "list": "List Setting Success.", + "get": "Get Setting Success.", + "getByName": "Get Setting by name Success.", + "update": "Update Succeed", + "error": { + "notFound": "Setting not found" + } +} diff --git a/packages/auth-api/src/message/languages/en/test.json b/packages/auth-api/src/message/languages/en/test.json new file mode 100644 index 0000000..5cac9c9 --- /dev/null +++ b/packages/auth-api/src/message/languages/en/test.json @@ -0,0 +1,3 @@ +{ + "hello": "This is test endpoint." +} diff --git a/packages/auth-api/src/message/languages/en/user.json b/packages/auth-api/src/message/languages/en/user.json new file mode 100644 index 0000000..3280114 --- /dev/null +++ b/packages/auth-api/src/message/languages/en/user.json @@ -0,0 +1,19 @@ +{ + "list": "List User Success.", + "get": "Get User Success.", + "create": "Create User Success.", + "delete": "Delete User Success.", + "update": "Update User Success.", + "profile": "Profile Success", + "upload": "Upload Success", + "inactive": "Inactive Succeed", + "active": "Active Succeed", + "error": { + "notFound": "User not found.", + "inactive": "User inactive", + "emailExist": "Email user used", + "mobileNumberExist": "Mobile Number user used", + "active": "User status active invalid", + "exist": "User exist" + } +} diff --git a/packages/auth-api/src/message/languages/id/request.json b/packages/auth-api/src/message/languages/id/request.json new file mode 100644 index 0000000..4cb43e1 --- /dev/null +++ b/packages/auth-api/src/message/languages/id/request.json @@ -0,0 +1,3 @@ +{ + "isEmail": "{property} harus berupa email." +} diff --git a/packages/auth-api/src/message/languages/id/test.json b/packages/auth-api/src/message/languages/id/test.json new file mode 100644 index 0000000..4857a4a --- /dev/null +++ b/packages/auth-api/src/message/languages/id/test.json @@ -0,0 +1,3 @@ +{ + "hello": "Ini adalah endpoint test" +} diff --git a/packages/auth-api/src/message/message.constant.ts b/packages/auth-api/src/message/message.constant.ts new file mode 100644 index 0000000..4e4e312 --- /dev/null +++ b/packages/auth-api/src/message/message.constant.ts @@ -0,0 +1,4 @@ +export enum ENUM_MESSAGE_LANGUAGE { + en = 'en', + id = 'id', +} diff --git a/packages/auth-api/src/message/message.interface.ts b/packages/auth-api/src/message/message.interface.ts new file mode 100644 index 0000000..0a4c60d --- /dev/null +++ b/packages/auth-api/src/message/message.interface.ts @@ -0,0 +1,9 @@ +export type IMessage = Record; + +export type IMessageOptionsProperties = Record; +export interface IMessageOptions { + readonly customLanguages?: string[]; + readonly properties?: IMessageOptionsProperties; +} + +export type IMessageSetOptions = Omit; diff --git a/packages/auth-api/src/message/message.module.ts b/packages/auth-api/src/message/message.module.ts new file mode 100644 index 0000000..2d34454 --- /dev/null +++ b/packages/auth-api/src/message/message.module.ts @@ -0,0 +1,32 @@ +import { Module, Global } from '@nestjs/common'; +import * as path from 'path'; +import { I18nModule, HeaderResolver, I18nJsonLoader } from 'nestjs-i18n'; +import { ConfigService } from '@nestjs/config'; +import { ENUM_MESSAGE_LANGUAGE } from './message.constant'; +import { MessageService } from './service/message.service'; + +@Global() +@Module({ + providers: [MessageService], + exports: [MessageService], + imports: [ + I18nModule.forRootAsync({ + useFactory: (configService: ConfigService) => ({ + fallbackLanguage: configService.get('app.language'), + fallbacks: Object.values(ENUM_MESSAGE_LANGUAGE).reduce( + (a, v) => ({ ...a, [`${v}-*`]: v }), + {} + ), + loaderOptions: { + path: path.join(__dirname, '/languages/'), + watch: true, + }, + }), + loader: I18nJsonLoader, + inject: [ConfigService], + resolvers: [new HeaderResolver(['x-custom-lang'])], + }), + ], + controllers: [], +}) +export class MessageModule {} diff --git a/packages/auth-api/src/message/service/message.service.ts b/packages/auth-api/src/message/service/message.service.ts new file mode 100644 index 0000000..1e20f7a --- /dev/null +++ b/packages/auth-api/src/message/service/message.service.ts @@ -0,0 +1,126 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { ENUM_MESSAGE_LANGUAGE } from 'src/message/message.constant'; +import { + IMessage, + IMessageOptions, + IMessageSetOptions, +} from '../message.interface'; +import { isArray, ValidationError } from 'class-validator'; +import { I18nService } from 'nestjs-i18n'; +import { IErrors } from 'src/utils/error/error.interface'; + +@Injectable() +export class MessageService { + private readonly defaultLanguage: string; + + constructor( + private readonly i18n: I18nService, + private readonly configService: ConfigService + ) { + this.defaultLanguage = this.configService.get('app.language'); + } + + async getRequestErrorsMessage( + requestErrors: ValidationError[], + customLanguages?: string[] + ): Promise { + const messages: Array = []; + for (const transfomer of requestErrors) { + let children: Record[] = transfomer.children; + let constraints: string[] = Object.keys( + transfomer.constraints || [] + ); + const errors: IErrors[] = []; + let property: string = transfomer.property; + let propertyValue: string = transfomer.value; + + if (children.length > 0) { + while (children.length > 0) { + for (const child of children) { + property = `${property}.${child.property}`; + + if (child.children && child.children.length > 0) { + children = child.children; + break; + } else if (child.constraints) { + constraints = Object.keys(child.constraints); + children = []; + propertyValue = child.value; + break; + } + } + } + } + + for (const constraint of constraints) { + errors.push({ + property: property, + message: (await this.get(`request.${constraint}`, { + customLanguages, + properties: { + property, + value: propertyValue, + }, + })) as string, + }); + } + + messages.push(errors); + } + + return messages.flat(1) as IErrors[]; + } + + async get( + key: string, + options?: IMessageOptions + ): Promise { + const { properties, customLanguages } = options + ? options + : { properties: undefined, customLanguages: undefined }; + + if ( + customLanguages && + isArray(customLanguages) && + customLanguages.length > 0 + ) { + const messages: IMessage = {}; + for (const customLanguage of customLanguages) { + messages[customLanguage] = await this.setMessage( + customLanguage, + key, + { + properties, + } + ); + } + + if (Object.keys(messages).length === 1) { + return messages[customLanguages[0]]; + } + + return messages; + } + + return this.setMessage(this.defaultLanguage, key, { + properties, + }); + } + + private setMessage( + lang: string, + key: string, + options?: IMessageSetOptions + ): any { + return this.i18n.translate(key, { + lang: lang, + args: + options && options.properties ? options.properties : undefined, + }); + } + + async getLanguages(): Promise { + return Object.values(ENUM_MESSAGE_LANGUAGE); + } +} diff --git a/packages/auth-api/src/pagination/pagination.abstract.ts b/packages/auth-api/src/pagination/pagination.abstract.ts new file mode 100644 index 0000000..92a9a82 --- /dev/null +++ b/packages/auth-api/src/pagination/pagination.abstract.ts @@ -0,0 +1,20 @@ +import { IPaginationSort } from './pagination.interface'; + +export abstract class PaginationListAbstract { + abstract search?: string; + abstract availableSearch?: string[]; + abstract page?: number; + abstract perPage: number; + abstract sort?: IPaginationSort; + abstract availableSort?: string[]; +} + +export abstract class PaginationSimpleListAbstract { + abstract search?: string; + abstract page?: number; + abstract perPage: number; +} + +export abstract class PaginationMiniListAbstract { + abstract perPage: number; +} diff --git a/packages/auth-api/src/pagination/pagination.constant.ts b/packages/auth-api/src/pagination/pagination.constant.ts new file mode 100644 index 0000000..e2eda76 --- /dev/null +++ b/packages/auth-api/src/pagination/pagination.constant.ts @@ -0,0 +1,17 @@ +export const PAGINATION_DEFAULT_PER_PAGE = 10; +export const PAGINATION_DEFAULT_MAX_PER_PAGE = 100; +export const PAGINATION_DEFAULT_PAGE = 1; +export const PAGINATION_DEFAULT_MAX_PAGE = 20; +export const PAGINATION_DEFAULT_SORT = 'createdAt@asc'; +export const PAGINATION_DEFAULT_AVAILABLE_SORT = ['createdAt']; + +export enum ENUM_PAGINATION_AVAILABLE_SORT_TYPE { + ASC = 1, + DESC = -1, +} + +export enum ENUM_PAGINATION_TYPE { + FUL = 'FUL', + SIMPLE = 'SIMPLE', + MINI = 'MINI', +} diff --git a/packages/auth-api/src/pagination/pagination.decorator.ts b/packages/auth-api/src/pagination/pagination.decorator.ts new file mode 100644 index 0000000..ff51c25 --- /dev/null +++ b/packages/auth-api/src/pagination/pagination.decorator.ts @@ -0,0 +1,200 @@ +import { applyDecorators, UsePipes } from '@nestjs/common'; +import { Expose, Transform, Type } from 'class-transformer'; +import { + IsBoolean, + IsMongoId, + IsOptional, + ValidateIf, + IsEnum, + IsNotEmpty, + IsDate, + IsString, +} from 'class-validator'; +import { RequestAddDatePipe } from 'src/utils/request/pipe/request.add-date.pipe'; +import { MinGreaterThan } from 'src/utils/request/validation/request.min-greater-than.validation'; +import { Skip } from 'src/utils/request/validation/request.skip.validation'; +import { + ENUM_PAGINATION_AVAILABLE_SORT_TYPE, + PAGINATION_DEFAULT_AVAILABLE_SORT, + PAGINATION_DEFAULT_MAX_PAGE, + PAGINATION_DEFAULT_MAX_PER_PAGE, + PAGINATION_DEFAULT_PAGE, + PAGINATION_DEFAULT_PER_PAGE, + PAGINATION_DEFAULT_SORT, +} from './pagination.constant'; +import { + IPaginationFilterDateOptions, + IPaginationFilterOptions, + IPaginationFilterStringOptions, +} from './pagination.interface'; + +export function PaginationSearch(): any { + return applyDecorators( + Expose(), + IsOptional(), + IsString(), + Transform(({ value }) => (value ? value : undefined)) + ); +} + +export function PaginationAvailableSearch(availableSearch: string[]): any { + return applyDecorators( + Expose(), + Transform(() => availableSearch) + ); +} + +export function PaginationPage(page = PAGINATION_DEFAULT_PAGE): any { + return applyDecorators( + Expose(), + Type(() => Number), + Transform(({ value }) => + !value + ? page + : value > PAGINATION_DEFAULT_MAX_PAGE + ? PAGINATION_DEFAULT_MAX_PAGE + : value + ) + ); +} + +export function PaginationPerPage(perPage = PAGINATION_DEFAULT_PER_PAGE): any { + return applyDecorators( + Expose(), + Type(() => Number), + Transform(({ value }) => + !value + ? perPage + : value > PAGINATION_DEFAULT_MAX_PER_PAGE + ? PAGINATION_DEFAULT_MAX_PER_PAGE + : value + ) + ); +} + +export function PaginationSort( + sort = PAGINATION_DEFAULT_SORT, + availableSort = PAGINATION_DEFAULT_AVAILABLE_SORT +): any { + return applyDecorators( + Expose(), + Transform(({ value, obj }) => { + const bSort = PAGINATION_DEFAULT_SORT.split('@')[0]; + + const rSort = value || sort; + const rAvailableSort = obj._availableSort || availableSort; + const field: string = rSort.split('@')[0]; + const type: string = rSort.split('@')[1]; + const convertField: string = rAvailableSort.includes(field) + ? field + : bSort; + const convertType: number = + type === 'desc' + ? ENUM_PAGINATION_AVAILABLE_SORT_TYPE.DESC + : ENUM_PAGINATION_AVAILABLE_SORT_TYPE.ASC; + + return { [convertField]: convertType }; + }) + ); +} + +export function PaginationAvailableSort( + availableSort = PAGINATION_DEFAULT_AVAILABLE_SORT +): any { + return applyDecorators( + Expose(), + Transform(({ value }) => (!value ? availableSort : value)) + ); +} + +export function PaginationFilterBoolean(defaultValue: boolean[]): any { + return applyDecorators( + Expose(), + IsBoolean({ each: true }), + Transform(({ value }) => + value + ? value + .split(',') + .map((val: string) => (val === 'true' ? true : false)) + : defaultValue + ) + ); +} + +export function PaginationFilterEnum( + defaultValue: T[], + defaultEnum: Record +): any { + const cEnum = defaultEnum as unknown; + return applyDecorators( + Expose(), + IsEnum(cEnum as object, { each: true }), + Transform(({ value }) => + value + ? value.split(',').map((val: string) => defaultEnum[val]) + : defaultValue + ) + ); +} + +export function PaginationFilterId( + field: string, + options?: IPaginationFilterOptions +): any { + return applyDecorators( + Expose(), + IsMongoId(), + options && options.required ? IsNotEmpty() : Skip(), + options && options.required + ? Skip() + : ValidateIf((e) => e[field] !== '' && e[field]) + ); +} + +export function PaginationFilterDate( + field: string, + options?: IPaginationFilterDateOptions +): any { + return applyDecorators( + Expose(), + IsDate(), + Type(() => Date), + options && options.required ? IsNotEmpty() : IsOptional(), + options && options.required + ? Skip() + : options.asEndDate + ? ValidateIf( + (e) => + e[field] !== '' && + e[options.asEndDate.moreThanField] !== '' && + e[field] && + e[options.asEndDate.moreThanField] + ) + : ValidateIf((e) => e[field] !== '' && e[field]), + options && options.asEndDate + ? MinGreaterThan(options.asEndDate.moreThanField) + : Skip(), + options && options.asEndDate ? UsePipes(RequestAddDatePipe(1)) : Skip() + ); +} + +export function PaginationFilterString( + field: string, + options?: IPaginationFilterStringOptions +) { + return applyDecorators( + Expose(), + IsString(), + options && options.lowercase + ? Transform(({ value }) => + value + ? value.split(',').map((val: string) => val.toLowerCase()) + : undefined + ) + : Skip(), + options && options.required ? IsNotEmpty() : IsOptional(), + options && options.required + ? Skip() + : ValidateIf((e) => e[field] !== '' && e[field]) + ); +} diff --git a/packages/auth-api/src/pagination/pagination.interface.ts b/packages/auth-api/src/pagination/pagination.interface.ts new file mode 100644 index 0000000..ace83bb --- /dev/null +++ b/packages/auth-api/src/pagination/pagination.interface.ts @@ -0,0 +1,27 @@ +import { ENUM_PAGINATION_AVAILABLE_SORT_TYPE } from './pagination.constant'; + +export interface IPaginationOptions { + limit: number; + skip: number; + sort?: Record; +} + +export type IPaginationSort = Record< + string, + ENUM_PAGINATION_AVAILABLE_SORT_TYPE +>; + +export interface IPaginationFilterOptions { + required?: boolean; +} + +export interface IPaginationFilterDateOptions extends IPaginationFilterOptions { + asEndDate?: { + moreThanField: string; + }; +} + +export interface IPaginationFilterStringOptions + extends IPaginationFilterOptions { + lowercase?: boolean; +} diff --git a/packages/auth-api/src/pagination/pagination.module.ts b/packages/auth-api/src/pagination/pagination.module.ts new file mode 100644 index 0000000..af7778e --- /dev/null +++ b/packages/auth-api/src/pagination/pagination.module.ts @@ -0,0 +1,10 @@ +import { Global, Module } from '@nestjs/common'; +import { PaginationService } from './service/pagination.service'; + +@Global() +@Module({ + providers: [PaginationService], + exports: [PaginationService], + imports: [], +}) +export class PaginationModule {} diff --git a/packages/auth-api/src/pagination/service/pagination.service.ts b/packages/auth-api/src/pagination/service/pagination.service.ts new file mode 100644 index 0000000..3829e41 --- /dev/null +++ b/packages/auth-api/src/pagination/service/pagination.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { + PAGINATION_DEFAULT_MAX_PAGE, + PAGINATION_DEFAULT_MAX_PER_PAGE, +} from '../pagination.constant'; + +@Injectable() +export class PaginationService { + async skip(page: number, perPage: number): Promise { + page = + page > PAGINATION_DEFAULT_MAX_PAGE + ? PAGINATION_DEFAULT_MAX_PAGE + : page; + perPage = + perPage > PAGINATION_DEFAULT_MAX_PER_PAGE + ? PAGINATION_DEFAULT_MAX_PER_PAGE + : perPage; + const skip: number = (page - 1) * perPage; + return skip; + } + + async totalPage(totalData: number, limit: number): Promise { + return Math.ceil(totalData / limit); + } +} diff --git a/packages/auth-api/src/permission/controller/permission.admin.controller.ts b/packages/auth-api/src/permission/controller/permission.admin.controller.ts new file mode 100644 index 0000000..4cf1a21 --- /dev/null +++ b/packages/auth-api/src/permission/controller/permission.admin.controller.ts @@ -0,0 +1,194 @@ +import { + Body, + Controller, + Get, + InternalServerErrorException, + Patch, + Put, + Query, +} from '@nestjs/common'; +import { ENUM_PERMISSIONS } from 'src/permission/permission.constant'; +import { AuthAdminJwtGuard } from 'src/auth/auth.decorator'; +import { PermissionService } from '../service/permission.service'; +import { + GetPermission, + PermissionGetGuard, + PermissionUpdateActiveGuard, + PermissionUpdateGuard, + PermissionUpdateInactiveGuard, +} from '../permission.decorator'; +import { + Response, + ResponsePaging, +} from 'src/utils/response/response.decorator'; +import { + IResponse, + IResponsePaging, +} from 'src/utils/response/response.interface'; +import { ENUM_STATUS_CODE_ERROR } from 'src/utils/error/error.constant'; +import { PaginationService } from 'src/pagination/service/pagination.service'; +import { PermissionDocument } from '../schema/permission.schema'; +import { PermissionListDto } from '../dto/permission.list.dto'; +import { PermissionUpdateDto } from '../dto/permission.update.dto'; +import { PermissionListSerialization } from '../serialization/permission.list.serialization'; +import { RequestParamGuard } from 'src/utils/request/request.decorator'; +import { PermissionRequestDto } from '../dto/permissions.request.dto'; +import { ErrorMeta } from 'src/utils/error/error.decorator'; + +@Controller({ + version: '1', + path: 'permission', +}) +export class PermissionAdminController { + constructor( + private readonly paginationService: PaginationService, + private readonly permissionService: PermissionService + ) {} + + @ResponsePaging('permission.list') + @AuthAdminJwtGuard(ENUM_PERMISSIONS.PERMISSION_READ) + @ErrorMeta(PermissionAdminController.name, 'list') + @Get('/list') + async list( + @Query() + { + page, + perPage, + sort, + search, + availableSort, + availableSearch, + isActive, + }: PermissionListDto + ): Promise { + const skip: number = await this.paginationService.skip(page, perPage); + const find: Record = { + isActive: { + $in: isActive, + }, + }; + if (search) { + find['$or'] = [ + { + name: { + $regex: new RegExp(search), + $options: 'i', + }, + }, + ]; + } + + const permissions: PermissionDocument[] = + await this.permissionService.findAll(find, { + skip: skip, + limit: perPage, + sort, + }); + + const totalData: number = await this.permissionService.getTotal(find); + const totalPage: number = await this.paginationService.totalPage( + totalData, + perPage + ); + + const data: PermissionListSerialization[] = + await this.permissionService.serializationList(permissions); + + return { + totalData, + totalPage, + currentPage: page, + perPage, + availableSearch, + availableSort, + data, + }; + } + + @Response('permission.get') + @PermissionGetGuard() + @RequestParamGuard(PermissionRequestDto) + @AuthAdminJwtGuard(ENUM_PERMISSIONS.PERMISSION_READ) + @ErrorMeta(PermissionAdminController.name, 'get') + @Get('/get/:permission') + async get( + @GetPermission() permission: PermissionDocument + ): Promise { + return this.permissionService.serializationGet(permission); + } + + @Response('permission.update') + @PermissionUpdateGuard() + @RequestParamGuard(PermissionRequestDto) + @AuthAdminJwtGuard( + ENUM_PERMISSIONS.PERMISSION_READ, + ENUM_PERMISSIONS.PERMISSION_UPDATE + ) + @Put('/update/:permission') + @ErrorMeta(PermissionAdminController.name, 'ErrorMeta') + async update( + @GetPermission() permission: PermissionDocument, + @Body() body: PermissionUpdateDto + ): Promise { + try { + await this.permissionService.update(permission._id, body); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + + return { + _id: permission._id, + }; + } + + @Response('permission.inactive') + @PermissionUpdateInactiveGuard() + @RequestParamGuard(PermissionRequestDto) + @AuthAdminJwtGuard( + ENUM_PERMISSIONS.PERMISSION_READ, + ENUM_PERMISSIONS.PERMISSION_UPDATE + ) + @ErrorMeta(PermissionAdminController.name, 'inactive') + @Patch('/update/:permission/inactive') + async inactive( + @GetPermission() permission: PermissionDocument + ): Promise { + try { + await this.permissionService.inactive(permission._id); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + + return; + } + + @Response('permission.active') + @PermissionUpdateActiveGuard() + @RequestParamGuard(PermissionRequestDto) + @AuthAdminJwtGuard( + ENUM_PERMISSIONS.PERMISSION_READ, + ENUM_PERMISSIONS.PERMISSION_UPDATE + ) + @ErrorMeta(PermissionAdminController.name, 'active') + @Patch('/update/:permission/active') + async active( + @GetPermission() permission: PermissionDocument + ): Promise { + try { + await this.permissionService.active(permission._id); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + + return; + } +} diff --git a/packages/auth-api/src/permission/dto/permission.list.dto.ts b/packages/auth-api/src/permission/dto/permission.list.dto.ts new file mode 100644 index 0000000..eefbda1 --- /dev/null +++ b/packages/auth-api/src/permission/dto/permission.list.dto.ts @@ -0,0 +1,42 @@ +import { PaginationListAbstract } from 'src/pagination/pagination.abstract'; +import { + PaginationAvailableSearch, + PaginationAvailableSort, + PaginationFilterBoolean, + PaginationPage, + PaginationPerPage, + PaginationSearch, + PaginationSort, +} from 'src/pagination/pagination.decorator'; +import { IPaginationSort } from 'src/pagination/pagination.interface'; +import { + PERMISSION_DEFAULT_ACTIVE, + PERMISSION_DEFAULT_AVAILABLE_SEARCH, + PERMISSION_DEFAULT_AVAILABLE_SORT, + PERMISSION_DEFAULT_PAGE, + PERMISSION_DEFAULT_PER_PAGE, + PERMISSION_DEFAULT_SORT, +} from '../permission.constant'; + +export class PermissionListDto implements PaginationListAbstract { + @PaginationSearch() + readonly search: string; + + @PaginationAvailableSearch(PERMISSION_DEFAULT_AVAILABLE_SEARCH) + readonly availableSearch: string[]; + + @PaginationPage(PERMISSION_DEFAULT_PAGE) + readonly page: number; + + @PaginationPerPage(PERMISSION_DEFAULT_PER_PAGE) + readonly perPage: number; + + @PaginationSort(PERMISSION_DEFAULT_SORT, PERMISSION_DEFAULT_AVAILABLE_SORT) + readonly sort: IPaginationSort; + + @PaginationAvailableSort(PERMISSION_DEFAULT_AVAILABLE_SORT) + readonly availableSort: string[]; + + @PaginationFilterBoolean(PERMISSION_DEFAULT_ACTIVE) + readonly isActive: string[]; +} diff --git a/packages/auth-api/src/permission/dto/permission.update.dto.ts b/packages/auth-api/src/permission/dto/permission.update.dto.ts new file mode 100644 index 0000000..85e8baa --- /dev/null +++ b/packages/auth-api/src/permission/dto/permission.update.dto.ts @@ -0,0 +1,12 @@ +import { IsString, IsNotEmpty, IsOptional, ValidateIf } from 'class-validator'; + +export class PermissionUpdateDto { + @IsString() + @IsNotEmpty() + readonly name: string; + + @IsString() + @IsOptional() + @ValidateIf((e) => e.description !== '') + readonly description?: string; +} diff --git a/packages/auth-api/src/permission/dto/permissions.request.dto.ts b/packages/auth-api/src/permission/dto/permissions.request.dto.ts new file mode 100644 index 0000000..33973fd --- /dev/null +++ b/packages/auth-api/src/permission/dto/permissions.request.dto.ts @@ -0,0 +1,9 @@ +import { Type } from 'class-transformer'; +import { IsNotEmpty, IsMongoId } from 'class-validator'; + +export class PermissionRequestDto { + @IsNotEmpty() + @IsMongoId() + @Type(() => String) + permission: string; +} diff --git a/packages/auth-api/src/permission/guard/payload/permission.default.guard.ts b/packages/auth-api/src/permission/guard/payload/permission.default.guard.ts new file mode 100644 index 0000000..e1a45b9 --- /dev/null +++ b/packages/auth-api/src/permission/guard/payload/permission.default.guard.ts @@ -0,0 +1,47 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + ForbiddenException, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { + ENUM_PERMISSIONS, + ENUM_PERMISSION_STATUS_CODE_ERROR, + PERMISSION_META_KEY, +} from 'src/permission/permission.constant'; +import { IPermission } from 'src/permission/permission.interface'; + +@Injectable() +export class PermissionPayloadDefaultGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + async canActivate(context: ExecutionContext): Promise { + const requiredPermission: ENUM_PERMISSIONS[] = + this.reflector.getAllAndOverride( + PERMISSION_META_KEY, + [context.getHandler(), context.getClass()] + ); + if (!requiredPermission) { + return true; + } + const { user } = context.switchToHttp().getRequest(); + const { role } = user; + const permissions: string[] = role.permissions + .filter((val: IPermission) => val.isActive) + .map((val: IPermission) => val.code); + + const hasPermission: boolean = requiredPermission.every((permission) => + permissions.includes(permission) + ); + + if (!hasPermission) { + throw new ForbiddenException({ + statusCode: + ENUM_PERMISSION_STATUS_CODE_ERROR.PERMISSION_GUARD_INVALID_ERROR, + message: 'permission.error.forbidden', + }); + } + return hasPermission; + } +} diff --git a/packages/auth-api/src/permission/guard/permission.active.guard.ts b/packages/auth-api/src/permission/guard/permission.active.guard.ts new file mode 100644 index 0000000..276b1b7 --- /dev/null +++ b/packages/auth-api/src/permission/guard/permission.active.guard.ts @@ -0,0 +1,38 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + BadRequestException, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { + ENUM_PERMISSION_STATUS_CODE_ERROR, + PERMISSION_ACTIVE_META_KEY, +} from '../permission.constant'; + +@Injectable() +export class PermissionActiveGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + async canActivate(context: ExecutionContext): Promise { + const required: boolean[] = this.reflector.getAllAndOverride( + PERMISSION_ACTIVE_META_KEY, + [context.getHandler(), context.getClass()] + ); + + if (!required) { + return true; + } + + const { __permission } = context.switchToHttp().getRequest(); + + if (!required.includes(__permission.isActive)) { + throw new BadRequestException({ + statusCode: + ENUM_PERMISSION_STATUS_CODE_ERROR.PERMISSION_ACTIVE_ERROR, + message: 'permission.error.active', + }); + } + return true; + } +} diff --git a/packages/auth-api/src/permission/guard/permission.not-found.guard.ts b/packages/auth-api/src/permission/guard/permission.not-found.guard.ts new file mode 100644 index 0000000..5ad3a96 --- /dev/null +++ b/packages/auth-api/src/permission/guard/permission.not-found.guard.ts @@ -0,0 +1,23 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + NotFoundException, +} from '@nestjs/common'; +import { ENUM_PERMISSION_STATUS_CODE_ERROR } from '../permission.constant'; + +@Injectable() +export class PermissionNotFoundGuard implements CanActivate { + async canActivate(context: ExecutionContext): Promise { + const { __permission } = context.switchToHttp().getRequest(); + + if (!__permission) { + throw new NotFoundException({ + statusCode: + ENUM_PERMISSION_STATUS_CODE_ERROR.PERMISSION_NOT_FOUND_ERROR, + message: 'permission.error.notFound', + }); + } + return true; + } +} diff --git a/packages/auth-api/src/permission/guard/permission.put-to-request.guard.ts b/packages/auth-api/src/permission/guard/permission.put-to-request.guard.ts new file mode 100644 index 0000000..c17a9c5 --- /dev/null +++ b/packages/auth-api/src/permission/guard/permission.put-to-request.guard.ts @@ -0,0 +1,20 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { PermissionDocument } from '../schema/permission.schema'; +import { PermissionService } from '../service/permission.service'; + +@Injectable() +export class PermissionPutToRequestGuard implements CanActivate { + constructor(private readonly permissionService: PermissionService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const { params } = request; + const { permission } = params; + + const check: PermissionDocument = + await this.permissionService.findOneById(permission); + request.__permission = check; + + return true; + } +} diff --git a/packages/auth-api/src/permission/permission.constant.ts b/packages/auth-api/src/permission/permission.constant.ts new file mode 100644 index 0000000..cfd19de --- /dev/null +++ b/packages/auth-api/src/permission/permission.constant.ts @@ -0,0 +1,31 @@ +export enum ENUM_PERMISSIONS { + USER_CREATE = 'USER_CREATE', + USER_UPDATE = 'USER_UPDATE', + USER_READ = 'USER_READ', + USER_DELETE = 'USER_DELETE', + ROLE_CREATE = 'ROLE_CREATE', + ROLE_UPDATE = 'ROLE_UPDATE', + ROLE_READ = 'ROLE_READ', + ROLE_DELETE = 'ROLE_DELETE', + PERMISSION_READ = 'PERMISSION_READ', + PERMISSION_UPDATE = 'PERMISSION_UPDATE', + SETTING_READ = 'SETTING_READ', + SETTING_UPDATE = 'SETTING_UPDATE', +} + +export const PERMISSION_META_KEY = 'PermissionMetaKey'; + +export const PERMISSION_ACTIVE_META_KEY = 'PermissionActiveMetaKey'; + +export enum ENUM_PERMISSION_STATUS_CODE_ERROR { + PERMISSION_NOT_FOUND_ERROR = 5200, + PERMISSION_GUARD_INVALID_ERROR = 5201, + PERMISSION_ACTIVE_ERROR = 5203, +} + +export const PERMISSION_DEFAULT_SORT = 'name@asc'; +export const PERMISSION_DEFAULT_PAGE = 1; +export const PERMISSION_DEFAULT_PER_PAGE = 10; +export const PERMISSION_DEFAULT_AVAILABLE_SORT = ['code', 'name', 'createdAt']; +export const PERMISSION_DEFAULT_AVAILABLE_SEARCH = ['code', 'name']; +export const PERMISSION_DEFAULT_ACTIVE = [true, false]; diff --git a/packages/auth-api/src/permission/permission.decorator.ts b/packages/auth-api/src/permission/permission.decorator.ts new file mode 100644 index 0000000..3eae4b2 --- /dev/null +++ b/packages/auth-api/src/permission/permission.decorator.ts @@ -0,0 +1,57 @@ +import { + applyDecorators, + createParamDecorator, + ExecutionContext, + SetMetadata, + UseGuards, +} from '@nestjs/common'; +import { PermissionActiveGuard } from './guard/permission.active.guard'; +import { PermissionNotFoundGuard } from './guard/permission.not-found.guard'; +import { PermissionPutToRequestGuard } from './guard/permission.put-to-request.guard'; +import { PERMISSION_ACTIVE_META_KEY } from './permission.constant'; + +export const GetPermission = createParamDecorator( + (data: string, ctx: ExecutionContext) => { + const { __permission } = ctx.switchToHttp().getRequest(); + return __permission; + } +); + +export function PermissionGetGuard(): any { + return applyDecorators( + UseGuards(PermissionPutToRequestGuard, PermissionNotFoundGuard) + ); +} + +export function PermissionUpdateGuard(): any { + return applyDecorators( + UseGuards( + PermissionPutToRequestGuard, + PermissionNotFoundGuard, + PermissionActiveGuard + ), + SetMetadata(PERMISSION_ACTIVE_META_KEY, [true]) + ); +} + +export function PermissionUpdateActiveGuard(): any { + return applyDecorators( + UseGuards( + PermissionPutToRequestGuard, + PermissionNotFoundGuard, + PermissionActiveGuard + ), + SetMetadata(PERMISSION_ACTIVE_META_KEY, [false]) + ); +} + +export function PermissionUpdateInactiveGuard(): any { + return applyDecorators( + UseGuards( + PermissionPutToRequestGuard, + PermissionNotFoundGuard, + PermissionActiveGuard + ), + SetMetadata(PERMISSION_ACTIVE_META_KEY, [true]) + ); +} diff --git a/packages/auth-api/src/permission/permission.interface.ts b/packages/auth-api/src/permission/permission.interface.ts new file mode 100644 index 0000000..9b4c8e2 --- /dev/null +++ b/packages/auth-api/src/permission/permission.interface.ts @@ -0,0 +1,6 @@ +export interface IPermission { + code: string; + name: string; + description?: string; + isActive?: boolean; +} diff --git a/packages/auth-api/src/permission/permission.module.ts b/packages/auth-api/src/permission/permission.module.ts new file mode 100644 index 0000000..063115f --- /dev/null +++ b/packages/auth-api/src/permission/permission.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { DATABASE_CONNECTION_NAME } from 'src/database/database.constant'; +import { + PermissionDatabaseName, + PermissionEntity, + PermissionSchema, +} from './schema/permission.schema'; +import { PermissionBulkService } from './service/permission.bulk.service'; +import { PermissionService } from './service/permission.service'; + +@Module({ + controllers: [], + providers: [PermissionService, PermissionBulkService], + exports: [PermissionService, PermissionBulkService], + imports: [ + MongooseModule.forFeature( + [ + { + name: PermissionEntity.name, + schema: PermissionSchema, + collection: PermissionDatabaseName, + }, + ], + DATABASE_CONNECTION_NAME + ), + ], +}) +export class PermissionModule {} diff --git a/packages/auth-api/src/permission/schema/permission.schema.ts b/packages/auth-api/src/permission/schema/permission.schema.ts new file mode 100644 index 0000000..e1553ed --- /dev/null +++ b/packages/auth-api/src/permission/schema/permission.schema.ts @@ -0,0 +1,43 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Document } from 'mongoose'; + +@Schema({ timestamps: true, versionKey: false }) +export class PermissionEntity { + @Prop({ + required: true, + index: true, + uppercase: true, + unique: true, + trim: true, + }) + code: string; + + @Prop({ + required: true, + lowercase: true, + }) + name: string; + + @Prop({ + required: false, + }) + description?: string; + + @Prop({ + required: true, + default: true, + }) + isActive: boolean; +} + +export const PermissionDatabaseName = 'permissions'; +export const PermissionSchema = SchemaFactory.createForClass(PermissionEntity); + +export type PermissionDocument = PermissionEntity & Document; + +// Hooks +PermissionSchema.pre('save', function (next) { + this.code = this.code.toUpperCase(); + this.name = this.name.toLowerCase(); + next(); +}); diff --git a/packages/auth-api/src/permission/serialization/permission.get.serialization.ts b/packages/auth-api/src/permission/serialization/permission.get.serialization.ts new file mode 100644 index 0000000..5a08457 --- /dev/null +++ b/packages/auth-api/src/permission/serialization/permission.get.serialization.ts @@ -0,0 +1,15 @@ +import { Exclude, Type } from 'class-transformer'; + +export class PermissionGetSerialization { + @Type(() => String) + readonly _id: string; + + readonly isActive: boolean; + readonly name: string; + readonly code: string; + readonly description: string; + readonly createdAt: Date; + + @Exclude() + readonly updatedAt: Date; +} diff --git a/packages/auth-api/src/permission/serialization/permission.list.serialization.ts b/packages/auth-api/src/permission/serialization/permission.list.serialization.ts new file mode 100644 index 0000000..a30f39d --- /dev/null +++ b/packages/auth-api/src/permission/serialization/permission.list.serialization.ts @@ -0,0 +1,14 @@ +import { Exclude, Type } from 'class-transformer'; +export class PermissionListSerialization { + @Type(() => String) + readonly _id: string; + + readonly isActive: boolean; + readonly name: string; + readonly code: string; + readonly description: string; + readonly createdAt: Date; + + @Exclude() + readonly updatedAt: Date; +} diff --git a/packages/auth-api/src/permission/service/permission.bulk.service.ts b/packages/auth-api/src/permission/service/permission.bulk.service.ts new file mode 100644 index 0000000..6f6bfd9 --- /dev/null +++ b/packages/auth-api/src/permission/service/permission.bulk.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; +import { Model } from 'mongoose'; +import { DatabaseEntity } from 'src/database/database.decorator'; +import { IPermission } from '../permission.interface'; +import { DeleteResult } from 'mongodb'; +import { + PermissionDocument, + PermissionEntity, +} from '../schema/permission.schema'; + +@Injectable() +export class PermissionBulkService { + constructor( + @DatabaseEntity(PermissionEntity.name) + private readonly permissionModel: Model + ) {} + + async createMany(data: IPermission[]): Promise { + return this.permissionModel.insertMany( + data.map(({ isActive, code, description, name }) => ({ + code: code, + name: name, + description: description, + isActive: isActive || true, + })) + ); + } + + async deleteMany(find: Record): Promise { + return this.permissionModel.deleteMany(find); + } +} diff --git a/packages/auth-api/src/permission/service/permission.service.ts b/packages/auth-api/src/permission/service/permission.service.ts new file mode 100644 index 0000000..00c5b57 --- /dev/null +++ b/packages/auth-api/src/permission/service/permission.service.ts @@ -0,0 +1,107 @@ +import { Injectable } from '@nestjs/common'; +import { plainToInstance } from 'class-transformer'; +import { Model } from 'mongoose'; +import { DatabaseEntity } from 'src/database/database.decorator'; +import { IDatabaseFindAllOptions } from 'src/database/database.interface'; +import { PermissionUpdateDto } from '../dto/permission.update.dto'; +import { IPermission } from '../permission.interface'; +import { + PermissionDocument, + PermissionEntity, +} from '../schema/permission.schema'; +import { PermissionGetSerialization } from '../serialization/permission.get.serialization'; +import { PermissionListSerialization } from '../serialization/permission.list.serialization'; + +@Injectable() +export class PermissionService { + constructor( + @DatabaseEntity(PermissionEntity.name) + private readonly permissionModel: Model + ) {} + + async findAll( + find?: Record, + options?: IDatabaseFindAllOptions + ): Promise { + const findAll = this.permissionModel.find(find); + if ( + options && + options.limit !== undefined && + options.skip !== undefined + ) { + findAll.limit(options.limit).skip(options.skip); + } + + if (options && options.sort) { + findAll.sort(options.sort); + } + + return findAll.lean(); + } + + async findOneById(_id: string): Promise { + return this.permissionModel.findById(_id).lean(); + } + + async findOne(find?: Record): Promise { + return this.permissionModel.findOne(find).lean(); + } + + async getTotal(find?: Record): Promise { + return this.permissionModel.countDocuments(find); + } + + async deleteOne(find: Record): Promise { + return this.permissionModel.findOneAndDelete(find); + } + + async create(data: IPermission): Promise { + const create: PermissionDocument = new this.permissionModel({ + name: data.name, + code: data.code, + description: data.description, + isActive: data.isActive || true, + }); + + return create.save(); + } + + async update( + _id: string, + { name, description }: PermissionUpdateDto + ): Promise { + const permission = await this.permissionModel.findById(_id); + + permission.name = name; + permission.description = description; + return permission.save(); + } + + async serializationGet( + data: PermissionDocument + ): Promise { + return plainToInstance(PermissionGetSerialization, data); + } + + async serializationList( + data: PermissionDocument[] + ): Promise { + return plainToInstance(PermissionListSerialization, data); + } + + async inactive(_id: string): Promise { + const permission: PermissionDocument = + await this.permissionModel.findById(_id); + + permission.isActive = false; + return permission.save(); + } + + async active(_id: string): Promise { + const permission: PermissionDocument = + await this.permissionModel.findById(_id); + + permission.isActive = true; + return permission.save(); + } +} diff --git a/packages/auth-api/src/role/controller/role.admin.controller.ts b/packages/auth-api/src/role/controller/role.admin.controller.ts new file mode 100644 index 0000000..8e4d2f5 --- /dev/null +++ b/packages/auth-api/src/role/controller/role.admin.controller.ts @@ -0,0 +1,278 @@ +import { + BadRequestException, + Body, + Controller, + Delete, + Get, + InternalServerErrorException, + NotFoundException, + Patch, + Post, + Put, + Query, +} from '@nestjs/common'; +import { + ENUM_PERMISSIONS, + ENUM_PERMISSION_STATUS_CODE_ERROR, +} from 'src/permission/permission.constant'; +import { AuthAdminJwtGuard } from 'src/auth/auth.decorator'; +import { PermissionService } from 'src/permission/service/permission.service'; +import { RoleService } from '../service/role.service'; +import { + GetRole, + RoleDeleteGuard, + RoleGetGuard, + RoleUpdateActiveGuard, + RoleUpdateGuard, + RoleUpdateInactiveGuard, +} from '../role.decorator'; +import { IRoleDocument } from '../role.interface'; +import { ENUM_ROLE_STATUS_CODE_ERROR } from '../role.constant'; +import { + Response, + ResponsePaging, +} from 'src/utils/response/response.decorator'; +import { + IResponse, + IResponsePaging, +} from 'src/utils/response/response.interface'; +import { ENUM_STATUS_CODE_ERROR } from 'src/utils/error/error.constant'; +import { PaginationService } from 'src/pagination/service/pagination.service'; +import { RoleDocument } from '../schema/role.schema'; +import { PermissionDocument } from 'src/permission/schema/permission.schema'; +import { RoleListDto } from '../dto/role.list.dto'; +import { RoleCreateDto } from '../dto/role.create.dto'; +import { RoleUpdateDto } from '../dto/role.update.dto'; +import { RoleListSerialization } from '../serialization/role.list.serialization'; +import { RoleRequestDto } from '../dto/role.request.dto'; +import { RequestParamGuard } from 'src/utils/request/request.decorator'; +import { ErrorMeta } from 'src/utils/error/error.decorator'; + +@Controller({ + version: '1', + path: 'role', +}) +export class RoleAdminController { + constructor( + private readonly paginationService: PaginationService, + private readonly roleService: RoleService, + private readonly permissionService: PermissionService + ) {} + + @ResponsePaging('role.list') + @AuthAdminJwtGuard(ENUM_PERMISSIONS.ROLE_READ) + @ErrorMeta(RoleAdminController.name, 'list') + @Get('/list') + async list( + @Query() + { + page, + perPage, + sort, + search, + availableSort, + availableSearch, + }: RoleListDto + ): Promise { + const skip: number = await this.paginationService.skip(page, perPage); + const find: Record = {}; + if (search) { + find['$or'] = [ + { + name: { + $regex: new RegExp(search), + $options: 'i', + }, + }, + ]; + } + + const roles: RoleDocument[] = await this.roleService.findAll(find, { + skip: skip, + limit: perPage, + sort, + }); + + const totalData: number = await this.roleService.getTotal({}); + const totalPage: number = await this.paginationService.totalPage( + totalData, + perPage + ); + + const data: RoleListSerialization[] = + await this.roleService.serializationList(roles); + + return { + totalData, + totalPage, + currentPage: page, + perPage, + availableSearch, + availableSort, + data, + }; + } + + @Response('role.get') + @RoleGetGuard() + @RequestParamGuard(RoleRequestDto) + @AuthAdminJwtGuard(ENUM_PERMISSIONS.ROLE_READ) + @ErrorMeta(RoleAdminController.name, 'get') + @Get('get/:role') + async get(@GetRole() role: IRoleDocument): Promise { + return this.roleService.serializationGet(role); + } + + @Response('role.create') + @AuthAdminJwtGuard(ENUM_PERMISSIONS.ROLE_READ, ENUM_PERMISSIONS.ROLE_CREATE) + @ErrorMeta(RoleAdminController.name, 'create') + @Post('/create') + async create( + @Body() + { name, permissions, isAdmin }: RoleCreateDto + ): Promise { + const exist: boolean = await this.roleService.exists(name); + if (exist) { + throw new BadRequestException({ + statusCode: ENUM_ROLE_STATUS_CODE_ERROR.ROLE_EXIST_ERROR, + message: 'role.error.exist', + }); + } + + for (const permission of permissions) { + const checkPermission: PermissionDocument = + await this.permissionService.findOneById(permission); + + if (!checkPermission) { + throw new NotFoundException({ + statusCode: + ENUM_PERMISSION_STATUS_CODE_ERROR.PERMISSION_NOT_FOUND_ERROR, + message: 'permission.error.notFound', + }); + } + } + + try { + const create = await this.roleService.create({ + name, + permissions, + isAdmin, + }); + + return { + _id: create._id, + }; + } catch (err: any) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + } + + @Response('role.update') + @RoleUpdateGuard() + @RequestParamGuard(RoleRequestDto) + @AuthAdminJwtGuard(ENUM_PERMISSIONS.ROLE_READ, ENUM_PERMISSIONS.ROLE_UPDATE) + @ErrorMeta(RoleAdminController.name, 'update') + @Put('/update/:role') + async update( + @GetRole() role: RoleDocument, + @Body() + { name, permissions, isAdmin }: RoleUpdateDto + ): Promise { + const check: boolean = await this.roleService.exists(name, role._id); + if (check) { + throw new BadRequestException({ + statusCode: ENUM_ROLE_STATUS_CODE_ERROR.ROLE_EXIST_ERROR, + message: 'role.error.exist', + }); + } + + for (const permission of permissions) { + const checkPermission: PermissionDocument = + await this.permissionService.findOneById(permission); + + if (!checkPermission) { + throw new NotFoundException({ + statusCode: + ENUM_PERMISSION_STATUS_CODE_ERROR.PERMISSION_NOT_FOUND_ERROR, + message: 'permission.error.notFound', + }); + } + } + + try { + await this.roleService.update(role._id, { + name, + permissions, + isAdmin, + }); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + + return { + _id: role._id, + }; + } + + @Response('role.delete') + @RoleDeleteGuard() + @RequestParamGuard(RoleRequestDto) + @AuthAdminJwtGuard(ENUM_PERMISSIONS.ROLE_READ, ENUM_PERMISSIONS.ROLE_DELETE) + @ErrorMeta(RoleAdminController.name, 'delete') + @Delete('/delete/:role') + async delete(@GetRole() role: IRoleDocument): Promise { + try { + await this.roleService.deleteOneById(role._id); + } catch (err) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + return; + } + + @Response('role.inactive') + @RoleUpdateInactiveGuard() + @RequestParamGuard(RoleRequestDto) + @AuthAdminJwtGuard(ENUM_PERMISSIONS.ROLE_READ, ENUM_PERMISSIONS.ROLE_UPDATE) + @ErrorMeta(RoleAdminController.name, 'inactive') + @Patch('/update/:role/inactive') + async inactive(@GetRole() role: IRoleDocument): Promise { + try { + await this.roleService.inactive(role._id); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + + return; + } + + @Response('role.active') + @RoleUpdateActiveGuard() + @RequestParamGuard(RoleRequestDto) + @AuthAdminJwtGuard(ENUM_PERMISSIONS.ROLE_READ, ENUM_PERMISSIONS.ROLE_UPDATE) + @ErrorMeta(RoleAdminController.name, 'active') + @Patch('/update/:role/active') + async active(@GetRole() role: IRoleDocument): Promise { + try { + await this.roleService.active(role._id); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + + return; + } +} diff --git a/packages/auth-api/src/role/dto/role.create.dto.ts b/packages/auth-api/src/role/dto/role.create.dto.ts new file mode 100644 index 0000000..b4d18f8 --- /dev/null +++ b/packages/auth-api/src/role/dto/role.create.dto.ts @@ -0,0 +1,26 @@ +import { Type } from 'class-transformer'; +import { + IsString, + IsNotEmpty, + MaxLength, + MinLength, + IsMongoId, + IsBoolean, +} from 'class-validator'; + +export class RoleCreateDto { + @IsString() + @IsNotEmpty() + @MinLength(3) + @MaxLength(30) + @Type(() => String) + readonly name: string; + + @IsMongoId({ each: true }) + @IsNotEmpty() + readonly permissions: string[]; + + @IsBoolean() + @IsNotEmpty() + readonly isAdmin: boolean; +} diff --git a/packages/auth-api/src/role/dto/role.list.dto.ts b/packages/auth-api/src/role/dto/role.list.dto.ts new file mode 100644 index 0000000..42f1771 --- /dev/null +++ b/packages/auth-api/src/role/dto/role.list.dto.ts @@ -0,0 +1,37 @@ +import { PaginationListAbstract } from 'src/pagination/pagination.abstract'; +import { + PaginationAvailableSearch, + PaginationAvailableSort, + PaginationPage, + PaginationPerPage, + PaginationSearch, + PaginationSort, +} from 'src/pagination/pagination.decorator'; +import { IPaginationSort } from 'src/pagination/pagination.interface'; +import { + ROLE_DEFAULT_AVAILABLE_SEARCH, + ROLE_DEFAULT_AVAILABLE_SORT, + ROLE_DEFAULT_PAGE, + ROLE_DEFAULT_PER_PAGE, + ROLE_DEFAULT_SORT, +} from '../role.constant'; + +export class RoleListDto implements PaginationListAbstract { + @PaginationSearch() + readonly search: string; + + @PaginationAvailableSearch(ROLE_DEFAULT_AVAILABLE_SEARCH) + readonly availableSearch: string[]; + + @PaginationPage(ROLE_DEFAULT_PAGE) + readonly page: number; + + @PaginationPerPage(ROLE_DEFAULT_PER_PAGE) + readonly perPage: number; + + @PaginationSort(ROLE_DEFAULT_SORT, ROLE_DEFAULT_AVAILABLE_SORT) + readonly sort: IPaginationSort; + + @PaginationAvailableSort(ROLE_DEFAULT_AVAILABLE_SORT) + readonly availableSort: string[]; +} diff --git a/packages/auth-api/src/role/dto/role.request.dto.ts b/packages/auth-api/src/role/dto/role.request.dto.ts new file mode 100644 index 0000000..44668e4 --- /dev/null +++ b/packages/auth-api/src/role/dto/role.request.dto.ts @@ -0,0 +1,9 @@ +import { Type } from 'class-transformer'; +import { IsNotEmpty, IsMongoId } from 'class-validator'; + +export class RoleRequestDto { + @IsNotEmpty() + @IsMongoId() + @Type(() => String) + role: string; +} diff --git a/packages/auth-api/src/role/dto/role.update.dto.ts b/packages/auth-api/src/role/dto/role.update.dto.ts new file mode 100644 index 0000000..b426ec8 --- /dev/null +++ b/packages/auth-api/src/role/dto/role.update.dto.ts @@ -0,0 +1,3 @@ +import { RoleCreateDto } from './role.create.dto'; + +export class RoleUpdateDto extends RoleCreateDto {} diff --git a/packages/auth-api/src/role/guard/role.active.guard.ts b/packages/auth-api/src/role/guard/role.active.guard.ts new file mode 100644 index 0000000..df6b815 --- /dev/null +++ b/packages/auth-api/src/role/guard/role.active.guard.ts @@ -0,0 +1,37 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + BadRequestException, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { + ENUM_ROLE_STATUS_CODE_ERROR, + ROLE_ACTIVE_META_KEY, +} from '../role.constant'; + +@Injectable() +export class RoleActiveGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + async canActivate(context: ExecutionContext): Promise { + const required: boolean[] = this.reflector.getAllAndOverride( + ROLE_ACTIVE_META_KEY, + [context.getHandler(), context.getClass()] + ); + + if (!required) { + return true; + } + + const { __role } = context.switchToHttp().getRequest(); + + if (!required.includes(__role.isActive)) { + throw new BadRequestException({ + statusCode: ENUM_ROLE_STATUS_CODE_ERROR.ROLE_ACTIVE_ERROR, + message: 'role.error.active', + }); + } + return true; + } +} diff --git a/packages/auth-api/src/role/guard/role.not-found.guard.ts b/packages/auth-api/src/role/guard/role.not-found.guard.ts new file mode 100644 index 0000000..be7ee1a --- /dev/null +++ b/packages/auth-api/src/role/guard/role.not-found.guard.ts @@ -0,0 +1,23 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + NotFoundException, +} from '@nestjs/common'; +import { ENUM_ROLE_STATUS_CODE_ERROR } from '../role.constant'; + +@Injectable() +export class RoleNotFoundGuard implements CanActivate { + async canActivate(context: ExecutionContext): Promise { + const { __role } = context.switchToHttp().getRequest(); + + if (!__role) { + throw new NotFoundException({ + statusCode: ENUM_ROLE_STATUS_CODE_ERROR.ROLE_NOT_FOUND_ERROR, + message: 'role.error.notFound', + }); + } + + return true; + } +} diff --git a/packages/auth-api/src/role/guard/role.put-to-request.guard.ts b/packages/auth-api/src/role/guard/role.put-to-request.guard.ts new file mode 100644 index 0000000..6998a53 --- /dev/null +++ b/packages/auth-api/src/role/guard/role.put-to-request.guard.ts @@ -0,0 +1,24 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { IRoleDocument } from '../role.interface'; +import { RoleService } from '../service/role.service'; + +@Injectable() +export class RolePutToRequestGuard implements CanActivate { + constructor(private readonly roleService: RoleService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const { params } = request; + const { role } = params; + + const check: IRoleDocument = + await this.roleService.findOneById(role, { + populate: { + permission: true, + }, + }); + request.__role = check; + + return true; + } +} diff --git a/packages/auth-api/src/role/guard/role.used.guard.ts b/packages/auth-api/src/role/guard/role.used.guard.ts new file mode 100644 index 0000000..4cd4142 --- /dev/null +++ b/packages/auth-api/src/role/guard/role.used.guard.ts @@ -0,0 +1,30 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + BadRequestException, +} from '@nestjs/common'; +import { ENUM_ROLE_STATUS_CODE_ERROR } from '../role.constant'; +import { Types } from 'mongoose'; +import { UserService } from 'src/user/service/user.service'; +import { UserDocument } from 'src/user/schema/user.schema'; + +@Injectable() +export class RoleUsedGuard implements CanActivate { + constructor(private readonly userService: UserService) {} + + async canActivate(context: ExecutionContext): Promise { + const { __role } = context.switchToHttp().getRequest(); + const check: UserDocument = await this.userService.findOne({ + role: new Types.ObjectId(__role._id), + }); + + if (check) { + throw new BadRequestException({ + statusCode: ENUM_ROLE_STATUS_CODE_ERROR.ROLE_USED_ERROR, + message: 'role.error.used', + }); + } + return true; + } +} diff --git a/packages/auth-api/src/role/role.constant.ts b/packages/auth-api/src/role/role.constant.ts new file mode 100644 index 0000000..1b8a60e --- /dev/null +++ b/packages/auth-api/src/role/role.constant.ts @@ -0,0 +1,15 @@ +export const ROLE_DEFAULT_SORT = 'name@asc'; +export const ROLE_DEFAULT_PAGE = 1; +export const ROLE_DEFAULT_PER_PAGE = 10; +export const ROLE_DEFAULT_AVAILABLE_SORT = ['name', 'createdAt']; +export const ROLE_DEFAULT_AVAILABLE_SEARCH = ['name']; + +export enum ENUM_ROLE_STATUS_CODE_ERROR { + ROLE_IS_INACTIVE_ERROR = 5500, + ROLE_NOT_FOUND_ERROR = 5501, + ROLE_EXIST_ERROR = 5502, + ROLE_ACTIVE_ERROR = 5503, + ROLE_USED_ERROR = 5504, +} + +export const ROLE_ACTIVE_META_KEY = 'RoleActiveMetaKey'; diff --git a/packages/auth-api/src/role/role.decorator.ts b/packages/auth-api/src/role/role.decorator.ts new file mode 100644 index 0000000..8f8a17d --- /dev/null +++ b/packages/auth-api/src/role/role.decorator.ts @@ -0,0 +1,56 @@ +import { + applyDecorators, + createParamDecorator, + ExecutionContext, + SetMetadata, + UseGuards, +} from '@nestjs/common'; +import { RoleActiveGuard } from './guard/role.active.guard'; +import { RoleNotFoundGuard } from './guard/role.not-found.guard'; +import { RolePutToRequestGuard } from './guard/role.put-to-request.guard'; +import { RoleUsedGuard } from './guard/role.used.guard'; +import { ROLE_ACTIVE_META_KEY } from './role.constant'; + +export const GetRole = createParamDecorator( + (data: string, ctx: ExecutionContext) => { + const { __role } = ctx.switchToHttp().getRequest(); + return __role; + } +); + +export function RoleGetGuard(): any { + return applyDecorators(UseGuards(RolePutToRequestGuard, RoleNotFoundGuard)); +} + +export function RoleUpdateGuard(): any { + return applyDecorators( + UseGuards(RolePutToRequestGuard, RoleNotFoundGuard, RoleActiveGuard), + SetMetadata(ROLE_ACTIVE_META_KEY, [true]) + ); +} + +export function RoleDeleteGuard(): any { + return applyDecorators( + UseGuards( + RolePutToRequestGuard, + RoleNotFoundGuard, + RoleActiveGuard, + RoleUsedGuard + ), + SetMetadata(ROLE_ACTIVE_META_KEY, [true]) + ); +} + +export function RoleUpdateActiveGuard(): any { + return applyDecorators( + UseGuards(RolePutToRequestGuard, RoleNotFoundGuard, RoleActiveGuard), + SetMetadata(ROLE_ACTIVE_META_KEY, [false]) + ); +} + +export function RoleUpdateInactiveGuard(): any { + return applyDecorators( + UseGuards(RolePutToRequestGuard, RoleNotFoundGuard, RoleActiveGuard), + SetMetadata(ROLE_ACTIVE_META_KEY, [true]) + ); +} diff --git a/packages/auth-api/src/role/role.interface.ts b/packages/auth-api/src/role/role.interface.ts new file mode 100644 index 0000000..408ec39 --- /dev/null +++ b/packages/auth-api/src/role/role.interface.ts @@ -0,0 +1,6 @@ +import { PermissionDocument } from 'src/permission/schema/permission.schema'; +import { RoleDocument } from './schema/role.schema'; + +export interface IRoleDocument extends Omit { + permissions: PermissionDocument[]; +} diff --git a/packages/auth-api/src/role/role.module.ts b/packages/auth-api/src/role/role.module.ts new file mode 100644 index 0000000..fff2ece --- /dev/null +++ b/packages/auth-api/src/role/role.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { DATABASE_CONNECTION_NAME } from 'src/database/database.constant'; +import { RoleDatabaseName, RoleEntity, RoleSchema } from './schema/role.schema'; +import { RoleBulkService } from './service/role.bulk.service'; +import { RoleService } from './service/role.service'; + +@Module({ + controllers: [], + providers: [RoleService, RoleBulkService], + exports: [RoleService, RoleBulkService], + imports: [ + MongooseModule.forFeature( + [ + { + name: RoleEntity.name, + schema: RoleSchema, + collection: RoleDatabaseName, + }, + ], + DATABASE_CONNECTION_NAME + ), + ], +}) +export class RoleModule {} diff --git a/packages/auth-api/src/role/schema/role.schema.ts b/packages/auth-api/src/role/schema/role.schema.ts new file mode 100644 index 0000000..af8fe4a --- /dev/null +++ b/packages/auth-api/src/role/schema/role.schema.ts @@ -0,0 +1,46 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Types, Document } from 'mongoose'; +import { PermissionEntity } from 'src/permission/schema/permission.schema'; + +@Schema({ timestamps: true, versionKey: false }) +export class RoleEntity { + @Prop({ + required: true, + index: true, + unique: true, + lowercase: true, + trim: true, + }) + name: string; + + @Prop({ + required: true, + type: Array, + default: [], + ref: PermissionEntity.name, + }) + permissions: Types.ObjectId[]; + + @Prop({ + required: true, + default: true, + }) + isActive: boolean; + + @Prop({ + required: true, + default: false, + }) + isAdmin: boolean; +} + +export const RoleDatabaseName = 'roles'; +export const RoleSchema = SchemaFactory.createForClass(RoleEntity); + +export type RoleDocument = RoleEntity & Document; + +// Hooks +RoleSchema.pre('save', function (next) { + this.name = this.name.toLowerCase(); + next(); +}); diff --git a/packages/auth-api/src/role/serialization/role.get.serialization.ts b/packages/auth-api/src/role/serialization/role.get.serialization.ts new file mode 100644 index 0000000..9c0c18b --- /dev/null +++ b/packages/auth-api/src/role/serialization/role.get.serialization.ts @@ -0,0 +1,26 @@ +import { Exclude, Transform, Type } from 'class-transformer'; +import { PermissionDocument } from 'src/permission/schema/permission.schema'; + +export class RoleGetSerialization { + @Type(() => String) + readonly _id: string; + + readonly isActive: boolean; + readonly name: string; + readonly isAdmin: boolean; + + @Transform(({ obj }) => + obj.permissions.map((val) => ({ + _id: `${val._id}`, + code: val.code, + name: val.name, + isActive: val.isActive, + })) + ) + readonly permissions: PermissionDocument[]; + + readonly createdAt: Date; + + @Exclude() + readonly updatedAt: Date; +} diff --git a/packages/auth-api/src/role/serialization/role.list.serialization.ts b/packages/auth-api/src/role/serialization/role.list.serialization.ts new file mode 100644 index 0000000..3ed4bb2 --- /dev/null +++ b/packages/auth-api/src/role/serialization/role.list.serialization.ts @@ -0,0 +1,18 @@ +import { Exclude, Type } from 'class-transformer'; + +export class RoleListSerialization { + @Type(() => String) + readonly _id: string; + + readonly isActive: boolean; + readonly name: string; + readonly isAdmin: boolean; + + @Exclude() + readonly permissions: number; + + readonly createdAt: Date; + + @Exclude() + readonly updatedAt: Date; +} diff --git a/packages/auth-api/src/role/service/role.bulk.service.ts b/packages/auth-api/src/role/service/role.bulk.service.ts new file mode 100644 index 0000000..8e82928 --- /dev/null +++ b/packages/auth-api/src/role/service/role.bulk.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { Model, Types } from 'mongoose'; +import { DatabaseEntity } from 'src/database/database.decorator'; +import { DeleteResult } from 'mongodb'; +import { RoleDocument, RoleEntity } from '../schema/role.schema'; +import { RoleCreateDto } from '../dto/role.create.dto'; + +@Injectable() +export class RoleBulkService { + constructor( + @DatabaseEntity(RoleEntity.name) + private readonly roleModel: Model + ) {} + + async deleteMany(find: Record): Promise { + return await this.roleModel.deleteMany(find); + } + + async createMany(data: RoleCreateDto[]): Promise { + return this.roleModel.insertMany( + data.map(({ name, permissions, isAdmin }) => ({ + name, + isActive: true, + isAdmin: isAdmin || false, + permissions: permissions.map((val) => new Types.ObjectId(val)), + })) + ); + } +} diff --git a/packages/auth-api/src/role/service/role.service.ts b/packages/auth-api/src/role/service/role.service.ts new file mode 100644 index 0000000..1906c83 --- /dev/null +++ b/packages/auth-api/src/role/service/role.service.ts @@ -0,0 +1,146 @@ +import { Injectable } from '@nestjs/common'; +import { Model, Types } from 'mongoose'; +import { plainToInstance } from 'class-transformer'; +import { DatabaseEntity } from 'src/database/database.decorator'; +import { IRoleDocument } from '../role.interface'; +import { RoleDocument, RoleEntity } from '../schema/role.schema'; +import { PermissionEntity } from 'src/permission/schema/permission.schema'; +import { + IDatabaseFindAllOptions, + IDatabaseFindOneOptions, +} from 'src/database/database.interface'; +import { RoleCreateDto } from '../dto/role.create.dto'; +import { RoleUpdateDto } from '../dto/role.update.dto'; +import { RoleGetSerialization } from '../serialization/role.get.serialization'; +import { RoleListSerialization } from '../serialization/role.list.serialization'; + +@Injectable() +export class RoleService { + constructor( + @DatabaseEntity(RoleEntity.name) + private readonly roleModel: Model + ) {} + + async findAll( + find?: Record, + options?: IDatabaseFindAllOptions + ): Promise { + const roles = this.roleModel.find(find); + if ( + options && + options.limit !== undefined && + options.skip !== undefined + ) { + roles.limit(options.limit).skip(options.skip); + } + + if (options && options.sort) { + roles.sort(options.sort); + } + + return roles.lean(); + } + + async getTotal(find?: Record): Promise { + return this.roleModel.countDocuments(find); + } + + async findOneById( + _id: string, + options?: IDatabaseFindOneOptions + ): Promise { + const roles = this.roleModel.findById(_id); + + if (options && options.populate && options.populate.permission) { + roles.populate({ + path: 'permissions', + model: PermissionEntity.name, + }); + } + + return roles.lean(); + } + + async findOne( + find?: Record, + options?: IDatabaseFindOneOptions + ): Promise { + const role = this.roleModel.findOne(find); + + if (options && options.populate && options.populate.permission) { + role.populate({ + path: 'permissions', + model: PermissionEntity.name, + }); + } + + return role.lean(); + } + + async exists(name: string, _id?: string): Promise { + const exist = await this.roleModel.exists({ + name: { + $regex: new RegExp(name), + $options: 'i', + }, + _id: { $nin: new Types.ObjectId(_id) }, + }); + + return exist ? true : false; + } + + async create({ + name, + permissions, + isAdmin, + }: RoleCreateDto): Promise { + const create: RoleDocument = new this.roleModel({ + name: name, + permissions: permissions.map((val) => new Types.ObjectId(val)), + isActive: true, + isAdmin: isAdmin || false, + }); + + return create.save(); + } + + async update( + _id: string, + { name, permissions, isAdmin }: RoleUpdateDto + ): Promise { + const update: RoleDocument = await this.roleModel.findById(_id); + update.name = name; + update.permissions = permissions.map((val) => new Types.ObjectId(val)); + update.isAdmin = isAdmin || false; + + return update.save(); + } + + async inactive(_id: string): Promise { + const role: RoleDocument = await this.roleModel.findById(_id); + + role.isActive = false; + return role.save(); + } + + async active(_id: string): Promise { + const role: RoleDocument = await this.roleModel.findById(_id); + + role.isActive = true; + return role.save(); + } + + async deleteOneById(_id: string): Promise { + return this.roleModel.findByIdAndDelete(_id); + } + + async serializationGet(data: IRoleDocument): Promise { + return plainToInstance(RoleGetSerialization, data); + } + + async serializationList( + data: RoleDocument[] + ): Promise { + return plainToInstance(RoleListSerialization, data); + } +} diff --git a/packages/auth-api/src/router/router.admin.module.ts b/packages/auth-api/src/router/router.admin.module.ts new file mode 100644 index 0000000..f3a19c5 --- /dev/null +++ b/packages/auth-api/src/router/router.admin.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { AuthModule } from 'src/auth/auth.module'; +import { PermissionAdminController } from 'src/permission/controller/permission.admin.controller'; +import { PermissionModule } from 'src/permission/permission.module'; +import { RoleAdminController } from 'src/role/controller/role.admin.controller'; +import { RoleModule } from 'src/role/role.module'; +import { SettingAdminController } from 'src/setting/controller/setting.admin.controller'; +import { UserAdminController } from 'src/user/controller/user.admin.controller'; +import { UserModule } from 'src/user/user.module'; + +@Module({ + controllers: [ + RoleAdminController, + UserAdminController, + PermissionAdminController, + SettingAdminController, + ], + providers: [], + exports: [], + imports: [AuthModule, RoleModule, UserModule, PermissionModule], +}) +export class RouterAdminModule {} diff --git a/packages/auth-api/src/router/router.callback.module.ts b/packages/auth-api/src/router/router.callback.module.ts new file mode 100644 index 0000000..7e4e003 --- /dev/null +++ b/packages/auth-api/src/router/router.callback.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; + +@Module({ + controllers: [], + providers: [], + exports: [], + imports: [], +}) +export class RouterCallbackModule {} diff --git a/packages/auth-api/src/router/router.common.module.ts b/packages/auth-api/src/router/router.common.module.ts new file mode 100644 index 0000000..9644029 --- /dev/null +++ b/packages/auth-api/src/router/router.common.module.ts @@ -0,0 +1,31 @@ +import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; +import { TerminusModule } from '@nestjs/terminus'; +import { AuthModule } from 'src/auth/auth.module'; +import { AuthCommonController } from 'src/auth/controller/auth.common.controller'; +import { HealthCommonController } from 'src/health/controller/health.common.controller'; +import { HealthModule } from 'src/health/health.module'; +import { PermissionModule } from 'src/permission/permission.module'; +import { RoleModule } from 'src/role/role.module'; +import { SettingCommonController } from 'src/setting/controller/setting.common.controller'; +import { UserModule } from 'src/user/user.module'; + +@Module({ + controllers: [ + AuthCommonController, + HealthCommonController, + SettingCommonController, + ], + providers: [], + exports: [], + imports: [ + UserModule, + AuthModule, + RoleModule, + PermissionModule, + TerminusModule, + HttpModule, + HealthModule, + ], +}) +export class RouterCommonModule {} diff --git a/packages/auth-api/src/router/router.enum.module.ts b/packages/auth-api/src/router/router.enum.module.ts new file mode 100644 index 0000000..cf702ed --- /dev/null +++ b/packages/auth-api/src/router/router.enum.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { MessageEnumController } from 'src/message/controller/message.enum.controller'; +import { MessageModule } from 'src/message/message.module'; + +@Module({ + controllers: [MessageEnumController], + providers: [], + exports: [], + imports: [MessageModule], +}) +export class RouterEnumModule {} diff --git a/packages/auth-api/src/router/router.public.module.ts b/packages/auth-api/src/router/router.public.module.ts new file mode 100644 index 0000000..07ac9f2 --- /dev/null +++ b/packages/auth-api/src/router/router.public.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { AuthModule } from 'src/auth/auth.module'; +import { AuthPublicController } from 'src/auth/controller/auth.public.controller'; +import { AwsModule } from 'src/aws/aws.module'; +import { PermissionModule } from 'src/permission/permission.module'; +import { RoleModule } from 'src/role/role.module'; +import { UserPublicController } from 'src/user/controller/user.public.controller'; +import { UserModule } from 'src/user/user.module'; + +@Module({ + controllers: [UserPublicController, AuthPublicController], + providers: [], + exports: [], + imports: [UserModule, AwsModule, AuthModule, RoleModule, PermissionModule], +}) +export class RouterPublicModule {} diff --git a/packages/auth-api/src/router/router.test.module.ts b/packages/auth-api/src/router/router.test.module.ts new file mode 100644 index 0000000..adc3854 --- /dev/null +++ b/packages/auth-api/src/router/router.test.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { TestingCommonController } from 'src/testing/controller/testing.common.controller'; + +@Module({ + controllers: [TestingCommonController], + providers: [], + exports: [], + imports: [], +}) +export class RouterTestModule {} diff --git a/packages/auth-api/src/setting/controller/setting.admin.controller.ts b/packages/auth-api/src/setting/controller/setting.admin.controller.ts new file mode 100644 index 0000000..c75fea5 --- /dev/null +++ b/packages/auth-api/src/setting/controller/setting.admin.controller.ts @@ -0,0 +1,54 @@ +import { + Body, + Controller, + InternalServerErrorException, + Put, +} from '@nestjs/common'; +import { AuthAdminJwtGuard } from 'src/auth/auth.decorator'; +import { ENUM_PERMISSIONS } from 'src/permission/permission.constant'; +import { ENUM_STATUS_CODE_ERROR } from 'src/utils/error/error.constant'; +import { ErrorMeta } from 'src/utils/error/error.decorator'; +import { RequestParamGuard } from 'src/utils/request/request.decorator'; +import { Response } from 'src/utils/response/response.decorator'; +import { IResponse } from 'src/utils/response/response.interface'; +import { SettingRequestDto } from '../dto/setting.request.dto'; +import { SettingUpdateDto } from '../dto/setting.update.dto'; +import { SettingDocument } from '../schema/setting.schema'; +import { SettingService } from '../service/setting.service'; +import { GetSetting, SettingUpdateGuard } from '../setting.decorator'; + +@Controller({ + version: '1', + path: 'setting', +}) +export class SettingAdminController { + constructor(private readonly settingService: SettingService) {} + + @Response('setting.update') + @SettingUpdateGuard() + @RequestParamGuard(SettingRequestDto) + @AuthAdminJwtGuard( + ENUM_PERMISSIONS.SETTING_READ, + ENUM_PERMISSIONS.SETTING_UPDATE + ) + @ErrorMeta(SettingAdminController.name, 'update') + @Put('/update/:setting') + async update( + @GetSetting() setting: SettingDocument, + @Body() + body: SettingUpdateDto + ): Promise { + try { + await this.settingService.updateOneById(setting._id, body); + } catch (err: any) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + + return { + _id: setting._id, + }; + } +} diff --git a/packages/auth-api/src/setting/controller/setting.common.controller.ts b/packages/auth-api/src/setting/controller/setting.common.controller.ts new file mode 100644 index 0000000..6140e08 --- /dev/null +++ b/packages/auth-api/src/setting/controller/setting.common.controller.ts @@ -0,0 +1,107 @@ +import { Controller, Get, Query } from '@nestjs/common'; +import { PaginationService } from 'src/pagination/service/pagination.service'; +import { ErrorMeta } from 'src/utils/error/error.decorator'; +import { RequestParamGuard } from 'src/utils/request/request.decorator'; +import { + Response, + ResponsePaging, +} from 'src/utils/response/response.decorator'; +import { + IResponse, + IResponsePaging, +} from 'src/utils/response/response.interface'; +import { SettingListDto } from '../dto/setting.list.dto'; +import { SettingRequestDto } from '../dto/setting.request.dto'; +import { SettingDocument } from '../schema/setting.schema'; +import { SettingListSerialization } from '../serialization/setting.list.serialization'; +import { SettingService } from '../service/setting.service'; +import { + GetSetting, + SettingGetByNameGuard, + SettingGetGuard, +} from '../setting.decorator'; + +@Controller({ + version: '1', + path: 'setting', +}) +export class SettingCommonController { + constructor( + private readonly settingService: SettingService, + private readonly paginationService: PaginationService + ) {} + + @ResponsePaging('setting.list') + @Get('/list') + @ErrorMeta(SettingCommonController.name, 'list') + async list( + @Query() + { + page, + perPage, + sort, + search, + availableSort, + availableSearch, + }: SettingListDto + ): Promise { + const skip: number = await this.paginationService.skip(page, perPage); + const find: Record = {}; + + if (search) { + find['$or'] = [ + { + name: { + $regex: new RegExp(search), + $options: 'i', + }, + }, + ]; + } + const settings: SettingDocument[] = await this.settingService.findAll( + find, + { + limit: perPage, + skip: skip, + sort, + } + ); + const totalData: number = await this.settingService.getTotal(find); + const totalPage: number = await this.paginationService.totalPage( + totalData, + perPage + ); + + const data: SettingListSerialization[] = + await this.settingService.serializationList(settings); + + return { + totalData, + totalPage, + currentPage: page, + perPage, + availableSearch, + availableSort, + data, + }; + } + + @Response('setting.get') + @SettingGetGuard() + @RequestParamGuard(SettingRequestDto) + @ErrorMeta(SettingCommonController.name, 'get') + @Get('get/:setting') + async get(@GetSetting() setting: SettingDocument): Promise { + return this.settingService.serializationGet(setting); + } + + @Response('setting.getByName') + @SettingGetByNameGuard() + @ErrorMeta(SettingCommonController.name, 'getByName') + @Get('get/name/:settingName') + async getByName( + @GetSetting() setting: SettingDocument + ): Promise { + return this.settingService.serializationGet(setting); + } +} diff --git a/packages/auth-api/src/setting/dto/setting.create.dto.ts b/packages/auth-api/src/setting/dto/setting.create.dto.ts new file mode 100644 index 0000000..307811d --- /dev/null +++ b/packages/auth-api/src/setting/dto/setting.create.dto.ts @@ -0,0 +1,22 @@ +import { Type } from 'class-transformer'; +import { IsString, IsNotEmpty, IsOptional, ValidateIf } from 'class-validator'; +import { SafeString } from 'src/utils/request/validation/request.safe-string.validation'; +import { StringOrNumberOrBoolean } from 'src/utils/request/validation/request.string-or-number-or-boolean.validation'; + +export class SettingCreateDto { + @IsString() + @IsNotEmpty() + @SafeString() + @Type(() => String) + readonly name: string; + + @IsString() + @IsOptional() + @Type(() => String) + @ValidateIf((e) => e.description !== '') + readonly description?: string; + + @IsNotEmpty() + @StringOrNumberOrBoolean() + readonly value: string | boolean | number; +} diff --git a/packages/auth-api/src/setting/dto/setting.list.dto.ts b/packages/auth-api/src/setting/dto/setting.list.dto.ts new file mode 100644 index 0000000..0916c2b --- /dev/null +++ b/packages/auth-api/src/setting/dto/setting.list.dto.ts @@ -0,0 +1,37 @@ +import { PaginationListAbstract } from 'src/pagination/pagination.abstract'; +import { + PaginationAvailableSearch, + PaginationAvailableSort, + PaginationPage, + PaginationPerPage, + PaginationSearch, + PaginationSort, +} from 'src/pagination/pagination.decorator'; +import { IPaginationSort } from 'src/pagination/pagination.interface'; +import { + SETTING_DEFAULT_AVAILABLE_SEARCH, + SETTING_DEFAULT_AVAILABLE_SORT, + SETTING_DEFAULT_PAGE, + SETTING_DEFAULT_PER_PAGE, + SETTING_DEFAULT_SORT, +} from '../setting.constant'; + +export class SettingListDto implements PaginationListAbstract { + @PaginationSearch() + readonly search: string; + + @PaginationAvailableSearch(SETTING_DEFAULT_AVAILABLE_SEARCH) + readonly availableSearch: string[]; + + @PaginationPage(SETTING_DEFAULT_PAGE) + readonly page: number; + + @PaginationPerPage(SETTING_DEFAULT_PER_PAGE) + readonly perPage: number; + + @PaginationSort(SETTING_DEFAULT_SORT, SETTING_DEFAULT_AVAILABLE_SORT) + readonly sort: IPaginationSort; + + @PaginationAvailableSort(SETTING_DEFAULT_AVAILABLE_SORT) + readonly availableSort: string[]; +} diff --git a/packages/auth-api/src/setting/dto/setting.request.dto.ts b/packages/auth-api/src/setting/dto/setting.request.dto.ts new file mode 100644 index 0000000..288ba05 --- /dev/null +++ b/packages/auth-api/src/setting/dto/setting.request.dto.ts @@ -0,0 +1,9 @@ +import { Type } from 'class-transformer'; +import { IsNotEmpty, IsMongoId } from 'class-validator'; + +export class SettingRequestDto { + @IsNotEmpty() + @IsMongoId() + @Type(() => String) + setting: string; +} diff --git a/packages/auth-api/src/setting/dto/setting.update.dto.ts b/packages/auth-api/src/setting/dto/setting.update.dto.ts new file mode 100644 index 0000000..4ab7dbb --- /dev/null +++ b/packages/auth-api/src/setting/dto/setting.update.dto.ts @@ -0,0 +1,15 @@ +import { Type } from 'class-transformer'; +import { IsString, IsNotEmpty, IsOptional, ValidateIf } from 'class-validator'; +import { StringOrNumberOrBoolean } from 'src/utils/request/validation/request.string-or-number-or-boolean.validation'; + +export class SettingUpdateDto { + @IsString() + @IsOptional() + @Type(() => String) + @ValidateIf((e) => e.description !== '') + readonly description?: string; + + @IsNotEmpty() + @StringOrNumberOrBoolean() + readonly value: string | boolean | number; +} diff --git a/packages/auth-api/src/setting/guard/setting.not-found.guard.ts b/packages/auth-api/src/setting/guard/setting.not-found.guard.ts new file mode 100644 index 0000000..fae92be --- /dev/null +++ b/packages/auth-api/src/setting/guard/setting.not-found.guard.ts @@ -0,0 +1,24 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + NotFoundException, +} from '@nestjs/common'; +import { ENUM_SETTING_STATUS_CODE_ERROR } from '../setting.constant'; + +@Injectable() +export class SettingNotFoundGuard implements CanActivate { + async canActivate(context: ExecutionContext): Promise { + const { __setting } = context.switchToHttp().getRequest(); + + if (!__setting) { + throw new NotFoundException({ + statusCode: + ENUM_SETTING_STATUS_CODE_ERROR.SETTING_NOT_FOUND_ERROR, + message: 'setting.error.notFound', + }); + } + + return true; + } +} diff --git a/packages/auth-api/src/setting/guard/setting.put-to-request.guard.ts b/packages/auth-api/src/setting/guard/setting.put-to-request.guard.ts new file mode 100644 index 0000000..089ac99 --- /dev/null +++ b/packages/auth-api/src/setting/guard/setting.put-to-request.guard.ts @@ -0,0 +1,39 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { SettingDocument } from '../schema/setting.schema'; +import { SettingService } from '../service/setting.service'; + +@Injectable() +export class SettingPutToRequestGuard implements CanActivate { + constructor(private readonly settingService: SettingService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const { params } = request; + const { setting } = params; + + const check: SettingDocument = await this.settingService.findOneById( + setting + ); + request.__setting = check; + + return true; + } +} + +@Injectable() +export class SettingPutToRequestByNameGuard implements CanActivate { + constructor(private readonly settingService: SettingService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const { params } = request; + const { settingName } = params; + + const check: SettingDocument = await this.settingService.findOneByName( + settingName + ); + request.__setting = check; + + return true; + } +} diff --git a/packages/auth-api/src/setting/schema/setting.schema.ts b/packages/auth-api/src/setting/schema/setting.schema.ts new file mode 100644 index 0000000..4feef59 --- /dev/null +++ b/packages/auth-api/src/setting/schema/setting.schema.ts @@ -0,0 +1,30 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Document, Schema as MongooseSchema } from 'mongoose'; + +@Schema({ timestamps: true, versionKey: false }) +export class SettingEntity { + @Prop({ + required: true, + index: true, + unique: true, + trim: true, + }) + name: string; + + @Prop({ + required: false, + }) + description?: string; + + @Prop({ + required: true, + trim: true, + type: MongooseSchema.Types.Mixed, + }) + value: string | number | boolean; +} + +export const SettingDatabaseName = 'settings'; +export const SettingSchema = SchemaFactory.createForClass(SettingEntity); + +export type SettingDocument = SettingEntity & Document; diff --git a/packages/auth-api/src/setting/serialization/setting.get.serialization.ts b/packages/auth-api/src/setting/serialization/setting.get.serialization.ts new file mode 100644 index 0000000..69d3918 --- /dev/null +++ b/packages/auth-api/src/setting/serialization/setting.get.serialization.ts @@ -0,0 +1,12 @@ +import { Type } from 'class-transformer'; + +export class SettingGetSerialization { + @Type(() => String) + readonly _id: string; + + readonly name: string; + readonly description?: string; + readonly value: string | number | boolean; + readonly createdAt: Date; + readonly updatedAt: Date; +} diff --git a/packages/auth-api/src/setting/serialization/setting.list.serialization.ts b/packages/auth-api/src/setting/serialization/setting.list.serialization.ts new file mode 100644 index 0000000..dcc8135 --- /dev/null +++ b/packages/auth-api/src/setting/serialization/setting.list.serialization.ts @@ -0,0 +1,12 @@ +import { Type } from 'class-transformer'; + +export class SettingListSerialization { + @Type(() => String) + readonly _id: string; + + readonly name: string; + readonly description?: string; + readonly value: string | number | boolean; + readonly createdAt: Date; + readonly updatedAt: Date; +} diff --git a/packages/auth-api/src/setting/service/setting.bulk.service.ts b/packages/auth-api/src/setting/service/setting.bulk.service.ts new file mode 100644 index 0000000..dda2bcc --- /dev/null +++ b/packages/auth-api/src/setting/service/setting.bulk.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { Model } from 'mongoose'; +import { DeleteResult } from 'mongodb'; +import { DatabaseEntity } from 'src/database/database.decorator'; +import { SettingDocument, SettingEntity } from '../schema/setting.schema'; + +@Injectable() +export class SettingBulkService { + constructor( + @DatabaseEntity(SettingEntity.name) + private readonly settingModel: Model + ) {} + + async deleteMany(find: Record): Promise { + return this.settingModel.deleteMany(find); + } +} diff --git a/packages/auth-api/src/setting/service/setting.service.ts b/packages/auth-api/src/setting/service/setting.service.ts new file mode 100644 index 0000000..a706cbb --- /dev/null +++ b/packages/auth-api/src/setting/service/setting.service.ts @@ -0,0 +1,109 @@ +import { Injectable } from '@nestjs/common'; +import { plainToInstance } from 'class-transformer'; +import { Model } from 'mongoose'; +import { DatabaseEntity } from 'src/database/database.decorator'; +import { IDatabaseFindAllOptions } from 'src/database/database.interface'; +import { HelperStringService } from 'src/utils/helper/service/helper.string.service'; +import { SettingCreateDto } from '../dto/setting.create.dto'; +import { SettingUpdateDto } from '../dto/setting.update.dto'; +import { SettingDocument, SettingEntity } from '../schema/setting.schema'; +import { SettingGetSerialization } from '../serialization/setting.get.serialization'; +import { SettingListSerialization } from '../serialization/setting.list.serialization'; + +@Injectable() +export class SettingService { + constructor( + @DatabaseEntity(SettingEntity.name) + private readonly settingModel: Model, + private readonly helperStringService: HelperStringService + ) {} + + async findAll( + find?: Record, + options?: IDatabaseFindAllOptions + ): Promise { + const settings = this.settingModel.find(find); + + if ( + options && + options.limit !== undefined && + options.skip !== undefined + ) { + settings.limit(options.limit).skip(options.skip); + } + + if (options && options.sort) { + settings.sort(options.sort); + } + + return settings.lean(); + } + + async getTotal(find?: Record): Promise { + return this.settingModel.countDocuments(find); + } + + async findOneById(_id: string): Promise { + return this.settingModel.findById(_id).lean(); + } + + async findOneByName(name: string): Promise { + return this.settingModel.findOne({ name }).lean(); + } + + async create({ + name, + description, + value, + }: SettingCreateDto): Promise { + const create: SettingDocument = new this.settingModel(); + + let convertValue = value; + if (typeof value === 'string') { + convertValue = await this.convertValue(value as string); + } + + create.name = name; + create.description = description; + create.value = convertValue; + return create.save(); + } + + async updateOneById( + _id: string, + { description, value }: SettingUpdateDto + ): Promise { + const update: SettingDocument = await this.settingModel.findById(_id); + + let convertValue = value; + if (typeof value === 'string') { + convertValue = await this.convertValue(value as string); + } + + update.description = description; + update.value = convertValue; + return update.save(); + } + + async serializationList( + data: SettingDocument[] + ): Promise { + return plainToInstance(SettingListSerialization, data); + } + + async serializationGet( + data: SettingDocument + ): Promise { + return plainToInstance(SettingGetSerialization, data); + } + + async deleteOne(find: Record): Promise { + return this.settingModel.findOneAndDelete(find); + } + + async convertValue(value: string): Promise { + return this.helperStringService.convertStringToNumberOrBooleanIfPossible( + value + ); + } +} diff --git a/packages/auth-api/src/setting/setting.constant.ts b/packages/auth-api/src/setting/setting.constant.ts new file mode 100644 index 0000000..b46c739 --- /dev/null +++ b/packages/auth-api/src/setting/setting.constant.ts @@ -0,0 +1,9 @@ +export const SETTING_DEFAULT_SORT = 'name@asc'; +export const SETTING_DEFAULT_AVAILABLE_SORT = ['name', 'createdAt']; +export const SETTING_DEFAULT_AVAILABLE_SEARCH = ['name']; +export const SETTING_DEFAULT_PAGE = 1; +export const SETTING_DEFAULT_PER_PAGE = 10; + +export enum ENUM_SETTING_STATUS_CODE_ERROR { + SETTING_NOT_FOUND_ERROR = 5900, +} diff --git a/packages/auth-api/src/setting/setting.decorator.ts b/packages/auth-api/src/setting/setting.decorator.ts new file mode 100644 index 0000000..dd0c801 --- /dev/null +++ b/packages/auth-api/src/setting/setting.decorator.ts @@ -0,0 +1,36 @@ +import { + applyDecorators, + createParamDecorator, + ExecutionContext, + UseGuards, +} from '@nestjs/common'; +import { SettingNotFoundGuard } from './guard/setting.not-found.guard'; +import { + SettingPutToRequestByNameGuard, + SettingPutToRequestGuard, +} from './guard/setting.put-to-request.guard'; + +export const GetSetting = createParamDecorator( + (data: string, ctx: ExecutionContext) => { + const { __setting } = ctx.switchToHttp().getRequest(); + return __setting; + } +); + +export function SettingGetGuard(): any { + return applyDecorators( + UseGuards(SettingPutToRequestGuard, SettingNotFoundGuard) + ); +} + +export function SettingGetByNameGuard(): any { + return applyDecorators( + UseGuards(SettingPutToRequestByNameGuard, SettingNotFoundGuard) + ); +} + +export function SettingUpdateGuard(): any { + return applyDecorators( + UseGuards(SettingPutToRequestGuard, SettingNotFoundGuard) + ); +} diff --git a/packages/auth-api/src/setting/setting.module.ts b/packages/auth-api/src/setting/setting.module.ts new file mode 100644 index 0000000..b597d33 --- /dev/null +++ b/packages/auth-api/src/setting/setting.module.ts @@ -0,0 +1,30 @@ +import { Global, Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { DATABASE_CONNECTION_NAME } from 'src/database/database.constant'; +import { SettingService } from './service/setting.service'; +import { + SettingDatabaseName, + SettingEntity, + SettingSchema, +} from './schema/setting.schema'; +import { SettingBulkService } from './service/setting.bulk.service'; + +@Global() +@Module({ + imports: [ + MongooseModule.forFeature( + [ + { + name: SettingEntity.name, + schema: SettingSchema, + collection: SettingDatabaseName, + }, + ], + DATABASE_CONNECTION_NAME + ), + ], + exports: [SettingService, SettingBulkService], + providers: [SettingService, SettingBulkService], + controllers: [], +}) +export class SettingModule {} diff --git a/packages/auth-api/src/task/task.module.ts b/packages/auth-api/src/task/task.module.ts new file mode 100644 index 0000000..751fc35 --- /dev/null +++ b/packages/auth-api/src/task/task.module.ts @@ -0,0 +1,25 @@ +import { DynamicModule, Module } from '@nestjs/common'; +import { ScheduleModule } from '@nestjs/schedule'; + +@Module({}) +export class TaskModule { + static register(): DynamicModule { + if (process.env.APP_TASK_ON === 'true') { + return { + module: TaskModule, + controllers: [], + providers: [], + exports: [], + imports: [ScheduleModule.forRoot()], + }; + } + + return { + module: TaskModule, + providers: [], + exports: [], + controllers: [], + imports: [], + }; + } +} diff --git a/packages/auth-api/src/testing/controller/testing.common.controller.ts b/packages/auth-api/src/testing/controller/testing.common.controller.ts new file mode 100644 index 0000000..82b7d90 --- /dev/null +++ b/packages/auth-api/src/testing/controller/testing.common.controller.ts @@ -0,0 +1,63 @@ +import { Controller, Get, VERSION_NEUTRAL } from '@nestjs/common'; +import { AuthExcludeApiKey } from 'src/auth/auth.decorator'; +import { ENUM_LOGGER_ACTION } from 'src/logger/logger.constant'; +import { Logger } from 'src/logger/logger.decorator'; +import { ErrorMeta } from 'src/utils/error/error.decorator'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { HelperService } from 'src/utils/helper/service/helper.service'; +import { + RequestTimezone, + RequestUserAgent, +} from 'src/utils/request/request.decorator'; +import { + Response, + ResponseTimeout, +} from 'src/utils/response/response.decorator'; +import { IResponse } from 'src/utils/response/response.interface'; +import { IResult } from 'ua-parser-js'; + +@Controller({ + version: VERSION_NEUTRAL, +}) +export class TestingCommonController { + constructor( + private readonly helperDateService: HelperDateService, + private readonly helperService: HelperService + ) {} + + @Response('test.hello') + @AuthExcludeApiKey() + @Logger(ENUM_LOGGER_ACTION.TEST, { tags: ['test'] }) + @ErrorMeta(TestingCommonController.name, 'hello') + @Get('/hello') + async hello( + @RequestUserAgent() userAgent: IResult, + @RequestTimezone() timezone: string + ): Promise { + const newDate = this.helperDateService.create({ + timezone: timezone, + }); + return { + userAgent, + date: newDate, + format: this.helperDateService.format(newDate, { + timezone: timezone, + }), + timestamp: this.helperDateService.timestamp({ + date: newDate, + timezone: timezone, + }), + }; + } + + @Response('test.helloTimeout') + @AuthExcludeApiKey() + @ResponseTimeout('10s') + @ErrorMeta(TestingCommonController.name, 'helloTimeout') + @Get('/hello/timeout') + async helloTimeout(): Promise { + await this.helperService.delay(60000); + + return {}; + } +} diff --git a/packages/auth-api/src/user/controller/user.admin.controller.ts b/packages/auth-api/src/user/controller/user.admin.controller.ts new file mode 100644 index 0000000..aae4822 --- /dev/null +++ b/packages/auth-api/src/user/controller/user.admin.controller.ts @@ -0,0 +1,279 @@ +import { + Controller, + Get, + Post, + Body, + Delete, + Put, + Query, + InternalServerErrorException, + BadRequestException, + Patch, + NotFoundException, +} from '@nestjs/common'; +import { ENUM_PERMISSIONS } from 'src/permission/permission.constant'; +import { + GetUser, + UserDeleteGuard, + UserGetGuard, + UserUpdateActiveGuard, + UserUpdateGuard, + UserUpdateInactiveGuard, +} from '../user.decorator'; +import { AuthAdminJwtGuard } from 'src/auth/auth.decorator'; +import { ENUM_ROLE_STATUS_CODE_ERROR } from 'src/role/role.constant'; +import { UserService } from '../service/user.service'; +import { RoleService } from 'src/role/service/role.service'; +import { IUserCheckExist, IUserDocument } from '../user.interface'; +import { ENUM_USER_STATUS_CODE_ERROR } from '../user.constant'; +import { PaginationService } from 'src/pagination/service/pagination.service'; +import { AuthService } from 'src/auth/service/auth.service'; +import { + Response, + ResponsePaging, +} from 'src/utils/response/response.decorator'; +import { + IResponse, + IResponsePaging, +} from 'src/utils/response/response.interface'; +import { ENUM_STATUS_CODE_ERROR } from 'src/utils/error/error.constant'; +import { UserListDto } from '../dto/user.list.dto'; +import { UserListSerialization } from '../serialization/user.list.serialization'; +import { UserCreateDto } from '../dto/user.create.dto'; +import { UserUpdateDto } from '../dto/user.update.dto'; +import { RequestParamGuard } from 'src/utils/request/request.decorator'; +import { UserRequestDto } from '../dto/user.request.dto'; +import { ErrorMeta } from 'src/utils/error/error.decorator'; + +@Controller({ + version: '1', + path: 'user', +}) +export class UserAdminController { + constructor( + private readonly authService: AuthService, + private readonly paginationService: PaginationService, + private readonly userService: UserService, + private readonly roleService: RoleService + ) {} + + @ResponsePaging('user.list') + @AuthAdminJwtGuard(ENUM_PERMISSIONS.USER_READ) + @ErrorMeta(UserAdminController.name, 'list') + @Get('/list') + async list( + @Query() + { + page, + perPage, + sort, + search, + availableSort, + availableSearch, + }: UserListDto + ): Promise { + const skip: number = await this.paginationService.skip(page, perPage); + const find: Record = {}; + + if (search) { + find['$or'] = [ + { + firstName: { + $regex: new RegExp(search), + $options: 'i', + }, + lastName: { + $regex: new RegExp(search), + $options: 'i', + }, + email: { + $regex: new RegExp(search), + $options: 'i', + }, + mobileNumber: search, + }, + ]; + } + const users: IUserDocument[] = await this.userService.findAll(find, { + limit: perPage, + skip: skip, + sort, + }); + const totalData: number = await this.userService.getTotal(find); + const totalPage: number = await this.paginationService.totalPage( + totalData, + perPage + ); + + const data: UserListSerialization[] = + await this.userService.serializationList(users); + + return { + totalData, + totalPage, + currentPage: page, + perPage, + availableSearch, + availableSort, + data, + }; + } + + @Response('user.get') + @UserGetGuard() + @RequestParamGuard(UserRequestDto) + @AuthAdminJwtGuard(ENUM_PERMISSIONS.USER_READ) + @ErrorMeta(UserAdminController.name, 'get') + @Get('get/:user') + async get(@GetUser() user: IUserDocument): Promise { + return this.userService.serializationGet(user); + } + + @Response('user.create') + @AuthAdminJwtGuard(ENUM_PERMISSIONS.USER_READ, ENUM_PERMISSIONS.USER_CREATE) + @ErrorMeta(UserAdminController.name, 'create') + @Post('/create') + async create( + @Body() + body: UserCreateDto + ): Promise { + const checkExist: IUserCheckExist = await this.userService.checkExist( + body.email, + body.mobileNumber + ); + + if (checkExist.email && checkExist.mobileNumber) { + throw new BadRequestException({ + statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_EXISTS_ERROR, + message: 'user.error.exist', + }); + } else if (checkExist.email) { + throw new BadRequestException({ + statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_EMAIL_EXIST_ERROR, + message: 'user.error.emailExist', + }); + } else if (checkExist.mobileNumber) { + throw new BadRequestException({ + statusCode: + ENUM_USER_STATUS_CODE_ERROR.USER_MOBILE_NUMBER_EXIST_ERROR, + message: 'user.error.mobileNumberExist', + }); + } + + const role = await this.roleService.findOneById(body.role); + if (!role) { + throw new NotFoundException({ + statusCode: ENUM_ROLE_STATUS_CODE_ERROR.ROLE_NOT_FOUND_ERROR, + message: 'role.error.notFound', + }); + } + + try { + const password = await this.authService.createPassword( + body.password + ); + + const create = await this.userService.create({ + firstName: body.firstName, + lastName: body.lastName, + email: body.email, + mobileNumber: body.mobileNumber, + role: body.role, + password: password.passwordHash, + passwordExpired: password.passwordExpired, + salt: password.salt, + }); + + return { + _id: create._id, + }; + } catch (err: any) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + } + + @Response('user.delete') + @UserDeleteGuard() + @RequestParamGuard(UserRequestDto) + @AuthAdminJwtGuard(ENUM_PERMISSIONS.USER_READ, ENUM_PERMISSIONS.USER_DELETE) + @ErrorMeta(UserAdminController.name, 'delete') + @Delete('/delete/:user') + async delete(@GetUser() user: IUserDocument): Promise { + try { + await this.userService.deleteOneById(user._id); + } catch (err) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + + return; + } + + @Response('user.update') + @UserUpdateGuard() + @RequestParamGuard(UserRequestDto) + @AuthAdminJwtGuard(ENUM_PERMISSIONS.USER_READ, ENUM_PERMISSIONS.USER_UPDATE) + @ErrorMeta(UserAdminController.name, 'update') + @Put('/update/:user') + async update( + @GetUser() user: IUserDocument, + @Body() + body: UserUpdateDto + ): Promise { + try { + await this.userService.updateOneById(user._id, body); + } catch (err: any) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + + return { + _id: user._id, + }; + } + + @Response('user.inactive') + @UserUpdateInactiveGuard() + @RequestParamGuard(UserRequestDto) + @AuthAdminJwtGuard(ENUM_PERMISSIONS.USER_READ, ENUM_PERMISSIONS.USER_UPDATE) + @ErrorMeta(UserAdminController.name, 'inactive') + @Patch('/update/:user/inactive') + async inactive(@GetUser() user: IUserDocument): Promise { + try { + await this.userService.inactive(user._id); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + + return; + } + + @Response('user.active') + @UserUpdateActiveGuard() + @RequestParamGuard(UserRequestDto) + @AuthAdminJwtGuard(ENUM_PERMISSIONS.USER_READ, ENUM_PERMISSIONS.USER_UPDATE) + @ErrorMeta(UserAdminController.name, 'active') + @Patch('/update/:user/active') + async active(@GetUser() user: IUserDocument): Promise { + try { + await this.userService.active(user._id); + } catch (e) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + + return; + } +} diff --git a/packages/auth-api/src/user/controller/user.public.controller.ts b/packages/auth-api/src/user/controller/user.public.controller.ts new file mode 100644 index 0000000..800c5a8 --- /dev/null +++ b/packages/auth-api/src/user/controller/user.public.controller.ts @@ -0,0 +1,80 @@ +import { + Controller, + Get, + HttpCode, + HttpStatus, + InternalServerErrorException, + Post, + UploadedFile, +} from '@nestjs/common'; +import { AuthPublicJwtGuard } from 'src/auth/auth.decorator'; +import { IAwsS3Response } from 'src/aws/aws.interface'; +import { AwsS3Service } from 'src/aws/service/aws.s3.service'; +import { ENUM_STATUS_CODE_ERROR } from 'src/utils/error/error.constant'; +import { ErrorMeta } from 'src/utils/error/error.decorator'; +import { ENUM_FILE_TYPE } from 'src/utils/file/file.constant'; +import { UploadFileSingle } from 'src/utils/file/file.decorator'; +import { Response } from 'src/utils/response/response.decorator'; +import { IResponse } from 'src/utils/response/response.interface'; +import { UserService } from '../service/user.service'; +import { GetUser, UserProfileGuard } from '../user.decorator'; +import { IUserDocument } from '../user.interface'; + +@Controller({ + version: '1', + path: 'user', +}) +export class UserPublicController { + constructor( + private readonly userService: UserService, + private readonly awsService: AwsS3Service + ) {} + + @Response('user.profile') + @UserProfileGuard() + @AuthPublicJwtGuard() + @ErrorMeta(UserPublicController.name, 'profile') + @Get('/profile') + async profile(@GetUser() user: IUserDocument): Promise { + return this.userService.serializationProfile(user); + } + + @Response('user.upload') + @UserProfileGuard() + @AuthPublicJwtGuard() + @UploadFileSingle('file', ENUM_FILE_TYPE.IMAGE) + @HttpCode(HttpStatus.OK) + @ErrorMeta(UserPublicController.name, 'upload') + @Post('/profile/upload') + async upload( + @GetUser() user: IUserDocument, + @UploadedFile() file: Express.Multer.File + ): Promise { + const filename: string = file.originalname; + const content: Buffer = file.buffer; + const mime: string = filename + .substring(filename.lastIndexOf('.') + 1, filename.length) + .toUpperCase(); + + const path = await this.userService.createRandomFilename(); + + try { + const aws: IAwsS3Response = await this.awsService.putItemInBucket( + `${path.filename}.${mime}`, + content, + { + path: `${path.path}/${user._id}`, + } + ); + + await this.userService.updatePhoto(user._id, aws); + } catch (err) { + throw new InternalServerErrorException({ + statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR, + message: 'http.serverError.internalServerError', + }); + } + + return; + } +} diff --git a/packages/auth-api/src/user/dto/user.create.dto.ts b/packages/auth-api/src/user/dto/user.create.dto.ts new file mode 100644 index 0000000..36ad3c8 --- /dev/null +++ b/packages/auth-api/src/user/dto/user.create.dto.ts @@ -0,0 +1,52 @@ +import { Type } from 'class-transformer'; +import { + IsString, + IsNotEmpty, + IsEmail, + MaxLength, + MinLength, + IsMongoId, + IsOptional, + ValidateIf, +} from 'class-validator'; +import { IsPasswordStrong } from 'src/utils/request/validation/request.is-password-strong.validation'; +import { IsStartWith } from 'src/utils/request/validation/request.is-start-with.validation'; + +export class UserCreateDto { + @IsEmail() + @IsNotEmpty() + @MaxLength(100) + @Type(() => String) + readonly email: string; + + @IsString() + @IsNotEmpty() + @MinLength(1) + @MaxLength(30) + @Type(() => String) + readonly firstName: string; + + @IsString() + @IsOptional() + @ValidateIf((e) => e.lastName !== '') + @MinLength(1) + @MaxLength(30) + @Type(() => String) + readonly lastName?: string; + + @IsString() + @IsNotEmpty() + @MinLength(10) + @MaxLength(14) + @Type(() => String) + @IsStartWith(['628']) + readonly mobileNumber: string; + + @IsNotEmpty() + @IsMongoId() + readonly role: string; + + @IsNotEmpty() + @IsPasswordStrong() + readonly password: string; +} diff --git a/packages/auth-api/src/user/dto/user.list.dto.ts b/packages/auth-api/src/user/dto/user.list.dto.ts new file mode 100644 index 0000000..385a13e --- /dev/null +++ b/packages/auth-api/src/user/dto/user.list.dto.ts @@ -0,0 +1,37 @@ +import { PaginationListAbstract } from 'src/pagination/pagination.abstract'; +import { + PaginationAvailableSearch, + PaginationAvailableSort, + PaginationPage, + PaginationPerPage, + PaginationSearch, + PaginationSort, +} from 'src/pagination/pagination.decorator'; +import { IPaginationSort } from 'src/pagination/pagination.interface'; +import { + USER_DEFAULT_AVAILABLE_SEARCH, + USER_DEFAULT_AVAILABLE_SORT, + USER_DEFAULT_PAGE, + USER_DEFAULT_PER_PAGE, + USER_DEFAULT_SORT, +} from '../user.constant'; + +export class UserListDto implements PaginationListAbstract { + @PaginationSearch() + readonly search: string; + + @PaginationAvailableSearch(USER_DEFAULT_AVAILABLE_SEARCH) + readonly availableSearch: string[]; + + @PaginationPage(USER_DEFAULT_PAGE) + readonly page: number; + + @PaginationPerPage(USER_DEFAULT_PER_PAGE) + readonly perPage: number; + + @PaginationSort(USER_DEFAULT_SORT, USER_DEFAULT_AVAILABLE_SORT) + readonly sort: IPaginationSort; + + @PaginationAvailableSort(USER_DEFAULT_AVAILABLE_SORT) + readonly availableSort: string[]; +} diff --git a/packages/auth-api/src/user/dto/user.request.dto.ts b/packages/auth-api/src/user/dto/user.request.dto.ts new file mode 100644 index 0000000..615243f --- /dev/null +++ b/packages/auth-api/src/user/dto/user.request.dto.ts @@ -0,0 +1,9 @@ +import { Type } from 'class-transformer'; +import { IsNotEmpty, IsMongoId } from 'class-validator'; + +export class UserRequestDto { + @IsNotEmpty() + @IsMongoId() + @Type(() => String) + user: string; +} diff --git a/packages/auth-api/src/user/dto/user.update.dto.ts b/packages/auth-api/src/user/dto/user.update.dto.ts new file mode 100644 index 0000000..a208978 --- /dev/null +++ b/packages/auth-api/src/user/dto/user.update.dto.ts @@ -0,0 +1,23 @@ +import { Type } from 'class-transformer'; +import { + IsString, + IsNotEmpty, + MaxLength, + IsOptional, + ValidateIf, +} from 'class-validator'; + +export class UserUpdateDto { + @IsString() + @IsNotEmpty() + @MaxLength(30) + @Type(() => String) + readonly firstName: string; + + @IsString() + @IsOptional() + @ValidateIf((e) => e.lastName !== '') + @MaxLength(30) + @Type(() => String) + readonly lastName?: string; +} diff --git a/packages/auth-api/src/user/guard/payload/user.payload.put-to-request.guard.ts b/packages/auth-api/src/user/guard/payload/user.payload.put-to-request.guard.ts new file mode 100644 index 0000000..fb65e3c --- /dev/null +++ b/packages/auth-api/src/user/guard/payload/user.payload.put-to-request.guard.ts @@ -0,0 +1,24 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { UserService } from 'src/user/service/user.service'; +import { IUserDocument } from 'src/user/user.interface'; + +@Injectable() +export class UserPayloadPutToRequestGuard implements CanActivate { + constructor(private readonly userService: UserService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const { user } = request; + + const check: IUserDocument = + await this.userService.findOneById(user._id, { + populate: { + role: true, + permission: true, + }, + }); + request.__user = check; + + return true; + } +} diff --git a/packages/auth-api/src/user/guard/user.active.guard.ts b/packages/auth-api/src/user/guard/user.active.guard.ts new file mode 100644 index 0000000..d264301 --- /dev/null +++ b/packages/auth-api/src/user/guard/user.active.guard.ts @@ -0,0 +1,37 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + BadRequestException, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { + ENUM_USER_STATUS_CODE_ERROR, + USER_ACTIVE_META_KEY, +} from '../user.constant'; + +@Injectable() +export class UserActiveGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + async canActivate(context: ExecutionContext): Promise { + const required: boolean[] = this.reflector.getAllAndOverride( + USER_ACTIVE_META_KEY, + [context.getHandler(), context.getClass()] + ); + + if (!required) { + return true; + } + + const { __user } = context.switchToHttp().getRequest(); + + if (!required.includes(__user.isActive)) { + throw new BadRequestException({ + statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_ACTIVE_ERROR, + message: 'user.error.active', + }); + } + return true; + } +} diff --git a/packages/auth-api/src/user/guard/user.not-found.guard.ts b/packages/auth-api/src/user/guard/user.not-found.guard.ts new file mode 100644 index 0000000..0d0ef63 --- /dev/null +++ b/packages/auth-api/src/user/guard/user.not-found.guard.ts @@ -0,0 +1,23 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + NotFoundException, +} from '@nestjs/common'; +import { ENUM_USER_STATUS_CODE_ERROR } from '../user.constant'; + +@Injectable() +export class UserNotFoundGuard implements CanActivate { + async canActivate(context: ExecutionContext): Promise { + const { __user } = context.switchToHttp().getRequest(); + + if (!__user) { + throw new NotFoundException({ + statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR, + message: 'user.error.notFound', + }); + } + + return true; + } +} diff --git a/packages/auth-api/src/user/guard/user.put-to-request.guard.ts b/packages/auth-api/src/user/guard/user.put-to-request.guard.ts new file mode 100644 index 0000000..8802802 --- /dev/null +++ b/packages/auth-api/src/user/guard/user.put-to-request.guard.ts @@ -0,0 +1,25 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { UserService } from '../service/user.service'; +import { IUserDocument } from '../user.interface'; + +@Injectable() +export class UserPutToRequestGuard implements CanActivate { + constructor(private readonly userService: UserService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const { params } = request; + const { user } = params; + + const check: IUserDocument = + await this.userService.findOneById(user, { + populate: { + role: true, + permission: true, + }, + }); + request.__user = check; + + return true; + } +} diff --git a/packages/auth-api/src/user/schema/user.schema.ts b/packages/auth-api/src/user/schema/user.schema.ts new file mode 100644 index 0000000..f88360e --- /dev/null +++ b/packages/auth-api/src/user/schema/user.schema.ts @@ -0,0 +1,98 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Types, Document } from 'mongoose'; +import { IAwsS3Response } from 'src/aws/aws.interface'; +import { RoleEntity } from 'src/role/schema/role.schema'; + +@Schema({ timestamps: true, versionKey: false }) +export class UserEntity { + @Prop({ + required: true, + index: true, + lowercase: true, + trim: true, + }) + firstName: string; + + @Prop({ + required: false, + index: true, + lowercase: true, + trim: true, + }) + lastName?: string; + + @Prop({ + required: true, + index: true, + unique: true, + trim: true, + }) + mobileNumber: string; + + @Prop({ + required: true, + index: true, + unique: true, + lowercase: true, + trim: true, + }) + email: string; + + @Prop({ + required: true, + type: Types.ObjectId, + ref: RoleEntity.name, + }) + role: Types.ObjectId; + + @Prop({ + required: true, + }) + password: string; + + @Prop({ + required: true, + }) + passwordExpired: Date; + + @Prop({ + required: true, + }) + salt: string; + + @Prop({ + required: true, + default: true, + }) + isActive: boolean; + + @Prop({ + required: false, + _id: false, + type: { + path: String, + pathWithFilename: String, + filename: String, + completedUrl: String, + baseUrl: String, + mime: String, + }, + }) + photo?: IAwsS3Response; +} + +export const UserDatabaseName = 'users'; +export const UserSchema = SchemaFactory.createForClass(UserEntity); + +export type UserDocument = UserEntity & Document; + +// Hooks +UserSchema.pre('save', function (next) { + this.email = this.email.toLowerCase(); + this.firstName = this.firstName.toLowerCase(); + + if (this.lastName) { + this.lastName = this.lastName.toLowerCase(); + } + next(); +}); diff --git a/packages/auth-api/src/user/serialization/user.get.serialization.ts b/packages/auth-api/src/user/serialization/user.get.serialization.ts new file mode 100644 index 0000000..c0f6128 --- /dev/null +++ b/packages/auth-api/src/user/serialization/user.get.serialization.ts @@ -0,0 +1,38 @@ +import { Exclude, Transform, Type } from 'class-transformer'; +import { IAwsS3Response } from 'src/aws/aws.interface'; +import { IRoleDocument } from 'src/role/role.interface'; + +export class UserGetSerialization { + @Type(() => String) + readonly _id: string; + + @Transform(({ value }) => ({ + name: value.name, + permissions: value.permissions.map((val: Record) => ({ + name: val.name, + isActive: val.isActive, + })), + isActive: value.isActive, + })) + readonly role: IRoleDocument; + + readonly email: string; + readonly mobileNumber: string; + readonly isActive: boolean; + readonly firstName: string; + readonly lastName: string; + readonly photo?: IAwsS3Response; + + @Exclude() + readonly password: string; + + readonly passwordExpired: Date; + + @Exclude() + readonly salt: string; + + readonly createdAt: Date; + + @Exclude() + readonly updatedAt: Date; +} diff --git a/packages/auth-api/src/user/serialization/user.list.serialization.ts b/packages/auth-api/src/user/serialization/user.list.serialization.ts new file mode 100644 index 0000000..7992943 --- /dev/null +++ b/packages/auth-api/src/user/serialization/user.list.serialization.ts @@ -0,0 +1,34 @@ +import { Exclude, Type } from 'class-transformer'; +import { Types } from 'mongoose'; +import { IAwsS3Response } from 'src/aws/aws.interface'; + +export class UserListSerialization { + @Type(() => String) + readonly _id: string; + + @Exclude() + readonly role: Types.ObjectId; + + readonly email: string; + readonly mobileNumber: string; + readonly isActive: boolean; + readonly firstName: string; + readonly lastName: string; + + @Exclude() + readonly photo?: IAwsS3Response; + + @Exclude() + readonly password: string; + + @Exclude() + readonly passwordExpired: Date; + + @Exclude() + readonly salt: string; + + readonly createdAt: Date; + + @Exclude() + readonly updatedAt: Date; +} diff --git a/packages/auth-api/src/user/serialization/user.profile.serialization.ts b/packages/auth-api/src/user/serialization/user.profile.serialization.ts new file mode 100644 index 0000000..ed47b74 --- /dev/null +++ b/packages/auth-api/src/user/serialization/user.profile.serialization.ts @@ -0,0 +1,38 @@ +import { Exclude, Transform, Type } from 'class-transformer'; +import { IAwsS3Response } from 'src/aws/aws.interface'; +import { IRoleDocument } from 'src/role/role.interface'; + +export class UserProfileSerialization { + @Type(() => String) + readonly _id: string; + + @Transform(({ value }) => ({ + name: value.name, + permissions: value.permissions.map((val: Record) => ({ + name: val.name, + isActive: val.isActive, + })), + isActive: value.isActive, + })) + readonly role: IRoleDocument; + + readonly firstName: string; + readonly lastName: string; + readonly email: string; + readonly mobileNumber: string; + readonly photo?: IAwsS3Response; + + @Exclude() + readonly password: string; + + readonly passwordExpired: Date; + + @Exclude() + readonly salt: string; + + @Exclude() + readonly createdAt: Date; + + @Exclude() + readonly updatedAt: Date; +} diff --git a/packages/auth-api/src/user/service/user.bulk.service.ts b/packages/auth-api/src/user/service/user.bulk.service.ts new file mode 100644 index 0000000..3ff72a9 --- /dev/null +++ b/packages/auth-api/src/user/service/user.bulk.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { Model } from 'mongoose'; +import { DatabaseEntity } from 'src/database/database.decorator'; +import { DeleteResult } from 'mongodb'; +import { UserDocument, UserEntity } from '../schema/user.schema'; + +@Injectable() +export class UserBulkService { + constructor( + @DatabaseEntity(UserEntity.name) + private readonly userModel: Model + ) {} + + async deleteMany(find: Record): Promise { + return this.userModel.deleteMany(find); + } +} diff --git a/packages/auth-api/src/user/service/user.service.ts b/packages/auth-api/src/user/service/user.service.ts new file mode 100644 index 0000000..dd33bf5 --- /dev/null +++ b/packages/auth-api/src/user/service/user.service.ts @@ -0,0 +1,261 @@ +import { Injectable } from '@nestjs/common'; +import { Model } from 'mongoose'; +import { + IUserDocument, + IUserCreate, + IUserUpdate, + IUserCheckExist, +} from 'src/user/user.interface'; +import { Types } from 'mongoose'; +import { plainToInstance } from 'class-transformer'; +import { IAwsS3Response } from 'src/aws/aws.interface'; +import { IAuthPassword } from 'src/auth/auth.interface'; +import { ConfigService } from '@nestjs/config'; +import { DatabaseEntity } from 'src/database/database.decorator'; +import { HelperStringService } from 'src/utils/helper/service/helper.string.service'; +import { UserDocument, UserEntity } from '../schema/user.schema'; +import { RoleEntity } from 'src/role/schema/role.schema'; +import { PermissionEntity } from 'src/permission/schema/permission.schema'; +import { + IDatabaseFindAllOptions, + IDatabaseFindOneOptions, +} from 'src/database/database.interface'; +import { UserProfileSerialization } from '../serialization/user.profile.serialization'; +import { UserListSerialization } from '../serialization/user.list.serialization'; +import { UserGetSerialization } from '../serialization/user.get.serialization'; + +@Injectable() +export class UserService { + private readonly uploadPath: string; + + constructor( + @DatabaseEntity(UserEntity.name) + private readonly userModel: Model, + private readonly helperStringService: HelperStringService, + private readonly configService: ConfigService + ) { + this.uploadPath = this.configService.get('user.uploadPath'); + } + + async findAll( + find?: Record, + options?: IDatabaseFindAllOptions + ): Promise { + const users = this.userModel.find(find).populate({ + path: 'role', + model: RoleEntity.name, + }); + + if ( + options && + options.limit !== undefined && + options.skip !== undefined + ) { + users.limit(options.limit).skip(options.skip); + } + + if (options && options.sort) { + users.sort(options.sort); + } + + return users.lean(); + } + + async getTotal(find?: Record): Promise { + return this.userModel.countDocuments(find); + } + + async serializationProfile( + data: IUserDocument + ): Promise { + return plainToInstance(UserProfileSerialization, data); + } + + async serializationList( + data: IUserDocument[] + ): Promise { + return plainToInstance(UserListSerialization, data); + } + + async serializationGet(data: IUserDocument): Promise { + return plainToInstance(UserGetSerialization, data); + } + + async findOneById( + _id: string, + options?: IDatabaseFindOneOptions + ): Promise { + const user = this.userModel.findById(_id); + + if (options && options.populate && options.populate.role) { + user.populate({ + path: 'role', + model: RoleEntity.name, + }); + + if (options.populate.permission) { + user.populate({ + path: 'role', + model: RoleEntity.name, + populate: { + path: 'permissions', + model: PermissionEntity.name, + }, + }); + } + } + + return user.lean(); + } + + async findOne( + find?: Record, + options?: IDatabaseFindOneOptions + ): Promise { + const user = this.userModel.findOne(find); + + if (options && options.populate && options.populate.role) { + user.populate({ + path: 'role', + model: RoleEntity.name, + }); + + if (options.populate.permission) { + user.populate({ + path: 'role', + model: RoleEntity.name, + populate: { + path: 'permissions', + model: PermissionEntity.name, + }, + }); + } + } + + return user.lean(); + } + + async create({ + firstName, + lastName, + password, + passwordExpired, + salt, + email, + mobileNumber, + role, + }: IUserCreate): Promise { + const user: UserEntity = { + firstName, + email, + mobileNumber, + password, + role: new Types.ObjectId(role), + isActive: true, + lastName: lastName || undefined, + salt, + passwordExpired, + }; + + const create: UserDocument = new this.userModel(user); + return create.save(); + } + + async deleteOneById(_id: string): Promise { + return this.userModel.findByIdAndDelete(_id); + } + + async deleteOne(find: Record): Promise { + return this.userModel.findOneAndDelete(find); + } + + async updateOneById( + _id: string, + { firstName, lastName }: IUserUpdate + ): Promise { + const user: UserDocument = await this.userModel.findById(_id); + + user.firstName = firstName; + user.lastName = lastName || undefined; + + return user.save(); + } + + async checkExist( + email: string, + mobileNumber: string, + _id?: string + ): Promise { + const existEmail: Record = await this.userModel.exists({ + email: { + $regex: new RegExp(email), + $options: 'i', + }, + _id: { $nin: [new Types.ObjectId(_id)] }, + }); + + const existMobileNumber: Record = + await this.userModel.exists({ + mobileNumber, + _id: { $nin: [new Types.ObjectId(_id)] }, + }); + + return { + email: existEmail ? true : false, + mobileNumber: existMobileNumber ? true : false, + }; + } + + async updatePhoto(_id: string, aws: IAwsS3Response): Promise { + const user: UserDocument = await this.userModel.findById(_id); + user.photo = aws; + + return user.save(); + } + + async createRandomFilename(): Promise> { + const filename: string = this.helperStringService.random(20); + + return { + path: this.uploadPath, + filename: filename, + }; + } + + async updatePassword( + _id: string, + { salt, passwordHash, passwordExpired }: IAuthPassword + ): Promise { + const auth: UserDocument = await this.userModel.findById(_id); + + auth.password = passwordHash; + auth.passwordExpired = passwordExpired; + auth.salt = salt; + + return auth.save(); + } + + async updatePasswordExpired( + _id: string, + passwordExpired: Date + ): Promise { + const auth: UserDocument = await this.userModel.findById(_id); + auth.passwordExpired = passwordExpired; + + return auth.save(); + } + + async inactive(_id: string): Promise { + const user: UserDocument = await this.userModel.findById(_id); + + user.isActive = false; + return user.save(); + } + + async active(_id: string): Promise { + const user: UserDocument = await this.userModel.findById(_id); + + user.isActive = true; + return user.save(); + } +} diff --git a/packages/auth-api/src/user/user.constant.ts b/packages/auth-api/src/user/user.constant.ts new file mode 100644 index 0000000..c9dc962 --- /dev/null +++ b/packages/auth-api/src/user/user.constant.ts @@ -0,0 +1,26 @@ +export enum ENUM_USER_STATUS_CODE_ERROR { + USER_NOT_FOUND_ERROR = 5400, + USER_EXISTS_ERROR = 5401, + USER_IS_INACTIVE_ERROR = 5402, + USER_EMAIL_EXIST_ERROR = 5403, + USER_MOBILE_NUMBER_EXIST_ERROR = 5404, + USER_ACTIVE_ERROR = 5405, +} + +export const USER_ACTIVE_META_KEY = 'UserActiveMetaKey'; +export const USER_DEFAULT_PAGE = 1; +export const USER_DEFAULT_PER_PAGE = 10; +export const USER_DEFAULT_SORT = 'name@asc'; +export const USER_DEFAULT_AVAILABLE_SORT = [ + 'firstName', + 'lastName', + 'email', + 'mobileNumber', + 'createdAt', +]; +export const USER_DEFAULT_AVAILABLE_SEARCH = [ + 'firstName', + 'lastName', + 'email', + 'mobileNumber', +]; diff --git a/packages/auth-api/src/user/user.decorator.ts b/packages/auth-api/src/user/user.decorator.ts new file mode 100644 index 0000000..9bc53f8 --- /dev/null +++ b/packages/auth-api/src/user/user.decorator.ts @@ -0,0 +1,51 @@ +import { + applyDecorators, + createParamDecorator, + ExecutionContext, + SetMetadata, + UseGuards, +} from '@nestjs/common'; +import { UserPayloadPutToRequestGuard } from './guard/payload/user.payload.put-to-request.guard'; +import { UserActiveGuard } from './guard/user.active.guard'; +import { UserNotFoundGuard } from './guard/user.not-found.guard'; +import { UserPutToRequestGuard } from './guard/user.put-to-request.guard'; +import { USER_ACTIVE_META_KEY } from './user.constant'; + +export const GetUser = createParamDecorator( + (data: string, ctx: ExecutionContext) => { + const { __user } = ctx.switchToHttp().getRequest(); + return __user; + } +); + +export function UserGetGuard(): any { + return applyDecorators(UseGuards(UserPutToRequestGuard, UserNotFoundGuard)); +} + +export function UserDeleteGuard(): any { + return applyDecorators(UseGuards(UserPutToRequestGuard, UserNotFoundGuard)); +} + +export function UserUpdateGuard(): any { + return applyDecorators(UseGuards(UserPutToRequestGuard, UserNotFoundGuard)); +} + +export function UserProfileGuard(): any { + return applyDecorators( + UseGuards(UserPayloadPutToRequestGuard, UserNotFoundGuard) + ); +} + +export function UserUpdateInactiveGuard(): any { + return applyDecorators( + UseGuards(UserPutToRequestGuard, UserNotFoundGuard, UserActiveGuard), + SetMetadata(USER_ACTIVE_META_KEY, [true]) + ); +} + +export function UserUpdateActiveGuard(): any { + return applyDecorators( + UseGuards(UserPutToRequestGuard, UserNotFoundGuard, UserActiveGuard), + SetMetadata(USER_ACTIVE_META_KEY, [false]) + ); +} diff --git a/packages/auth-api/src/user/user.interface.ts b/packages/auth-api/src/user/user.interface.ts new file mode 100644 index 0000000..db0df37 --- /dev/null +++ b/packages/auth-api/src/user/user.interface.ts @@ -0,0 +1,24 @@ +import { IRoleDocument } from 'src/role/role.interface'; +import { UserDocument } from './schema/user.schema'; + +export interface IUserDocument extends Omit { + role: IRoleDocument; +} + +export interface IUserCreate { + firstName: string; + lastName?: string; + password: string; + passwordExpired: Date; + email: string; + mobileNumber: string; + role: string; + salt: string; +} + +export type IUserUpdate = Pick; + +export interface IUserCheckExist { + email: boolean; + mobileNumber: boolean; +} diff --git a/packages/auth-api/src/user/user.module.ts b/packages/auth-api/src/user/user.module.ts new file mode 100644 index 0000000..2a0829e --- /dev/null +++ b/packages/auth-api/src/user/user.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { DATABASE_CONNECTION_NAME } from 'src/database/database.constant'; +import { UserService } from './service/user.service'; +import { UserBulkService } from './service/user.bulk.service'; +import { UserDatabaseName, UserEntity, UserSchema } from './schema/user.schema'; + +@Module({ + imports: [ + MongooseModule.forFeature( + [ + { + name: UserEntity.name, + schema: UserSchema, + collection: UserDatabaseName, + }, + ], + DATABASE_CONNECTION_NAME + ), + ], + exports: [UserService, UserBulkService], + providers: [UserService, UserBulkService], + controllers: [], +}) +export class UserModule {} diff --git a/packages/auth-api/src/utils/error/error.constant.ts b/packages/auth-api/src/utils/error/error.constant.ts new file mode 100644 index 0000000..c25a4da --- /dev/null +++ b/packages/auth-api/src/utils/error/error.constant.ts @@ -0,0 +1,8 @@ +export enum ENUM_STATUS_CODE_ERROR { + UNKNOWN_ERROR = 5990, + SERVICE_UNAVAILABLE = 5991, + REQUEST_TIMEOUT = 5992, +} + +export const ERROR_CLASS_META_KEY = 'ErrorClassMetaKey'; +export const ERROR_FUNCTION_META_KEY = 'ErrorFunctionMetaKey'; diff --git a/packages/auth-api/src/utils/error/error.decorator.ts b/packages/auth-api/src/utils/error/error.decorator.ts new file mode 100644 index 0000000..0f1231d --- /dev/null +++ b/packages/auth-api/src/utils/error/error.decorator.ts @@ -0,0 +1,12 @@ +import { applyDecorators, SetMetadata } from '@nestjs/common'; +import { + ERROR_CLASS_META_KEY, + ERROR_FUNCTION_META_KEY, +} from './error.constant'; + +export function ErrorMeta(cls: string, func: string): any { + return applyDecorators( + SetMetadata(ERROR_CLASS_META_KEY, cls), + SetMetadata(ERROR_FUNCTION_META_KEY, func) + ); +} diff --git a/packages/auth-api/src/utils/error/error.interface.ts b/packages/auth-api/src/utils/error/error.interface.ts new file mode 100644 index 0000000..1ec511b --- /dev/null +++ b/packages/auth-api/src/utils/error/error.interface.ts @@ -0,0 +1,14 @@ +import { IMessageOptionsProperties } from 'src/message/message.interface'; + +export interface IErrors { + readonly message: string; + readonly property: string; +} + +export interface IErrorException { + statusCode: number; + message: string; + errors?: IErrors[]; + data?: Record; + properties?: IMessageOptionsProperties; +} diff --git a/packages/auth-api/src/utils/error/error.module.ts b/packages/auth-api/src/utils/error/error.module.ts new file mode 100644 index 0000000..28910b3 --- /dev/null +++ b/packages/auth-api/src/utils/error/error.module.ts @@ -0,0 +1,28 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER, APP_INTERCEPTOR, Reflector } from '@nestjs/core'; +import { DebuggerService } from 'src/debugger/service/debugger.service'; +import { MessageService } from 'src/message/service/message.service'; +import { ErrorHttpFilter } from './filter/error.filter'; +import { ErrorLogInterceptor } from './interceptor/error.log.interceptor'; + +@Module({ + controllers: [], + providers: [ + { + provide: APP_FILTER, + inject: [MessageService], + useFactory: (messageService: MessageService) => + new ErrorHttpFilter(messageService), + }, + { + provide: APP_INTERCEPTOR, + inject: [Reflector, DebuggerService], + useFactory: ( + reflector: Reflector, + debuggerService: DebuggerService + ) => new ErrorLogInterceptor(reflector, debuggerService), + }, + ], + imports: [], +}) +export class ErrorModule {} diff --git a/packages/auth-api/src/utils/error/exception/error.success.exception.ts b/packages/auth-api/src/utils/error/exception/error.success.exception.ts new file mode 100644 index 0000000..7f7d22e --- /dev/null +++ b/packages/auth-api/src/utils/error/exception/error.success.exception.ts @@ -0,0 +1,18 @@ +import { HttpException, HttpStatus } from '@nestjs/common'; + +// in case if we want to return success, with custom status code +export class SuccessException extends HttpException { + constructor( + data: Record | string, + httpCode?: + | HttpStatus.OK + | HttpStatus.CREATED + | HttpStatus.ACCEPTED + | HttpStatus.NON_AUTHORITATIVE_INFORMATION + | HttpStatus.NO_CONTENT + | HttpStatus.RESET_CONTENT + | HttpStatus.PARTIAL_CONTENT + ) { + super(data, httpCode || HttpStatus.OK); + } +} diff --git a/packages/auth-api/src/utils/error/filter/error.filter.ts b/packages/auth-api/src/utils/error/filter/error.filter.ts new file mode 100644 index 0000000..7bbf846 --- /dev/null +++ b/packages/auth-api/src/utils/error/filter/error.filter.ts @@ -0,0 +1,65 @@ +import { + ExceptionFilter, + Catch, + ArgumentsHost, + HttpException, +} from '@nestjs/common'; +import { HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { Response } from 'express'; +import { IMessage } from 'src/message/message.interface'; +import { MessageService } from 'src/message/service/message.service'; +import { IRequestApp } from 'src/utils/request/request.interface'; +import { IErrorException } from '../error.interface'; + +@Catch(HttpException) +export class ErrorHttpFilter implements ExceptionFilter { + constructor(private readonly messageService: MessageService) {} + + async catch(exception: HttpException, host: ArgumentsHost): Promise { + const ctx: HttpArgumentsHost = host.switchToHttp(); + const statusHttp: number = exception.getStatus(); + const responseExpress: Response = ctx.getResponse(); + const { customLang } = ctx.getRequest(); + const customLanguages: string[] = customLang.split(','); + + // Restructure + const response = exception.getResponse() as IErrorException; + if (typeof response === 'object') { + const { statusCode, message, errors, data, properties } = response; + const rErrors = errors + ? await this.messageService.getRequestErrorsMessage( + errors, + customLanguages + ) + : undefined; + + let rMessage: string | IMessage = await this.messageService.get( + message, + { customLanguages } + ); + + if (properties) { + rMessage = await this.messageService.get(message, { + customLanguages, + properties, + }); + } + + responseExpress.status(statusHttp).json({ + statusCode, + message: rMessage, + errors: rErrors, + data, + }); + } else { + const rMessage: string | IMessage = await this.messageService.get( + 'response.error.structure', + { customLanguages } + ); + responseExpress.status(statusHttp).json({ + statusCode: 500, + message: rMessage, + }); + } + } +} diff --git a/packages/auth-api/src/utils/error/interceptor/error.log.interceptor.ts b/packages/auth-api/src/utils/error/interceptor/error.log.interceptor.ts new file mode 100644 index 0000000..4202a87 --- /dev/null +++ b/packages/auth-api/src/utils/error/interceptor/error.log.interceptor.ts @@ -0,0 +1,58 @@ +import { + CallHandler, + ExecutionContext, + HttpException, + NestInterceptor, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { catchError, Observable, throwError } from 'rxjs'; +import { DebuggerService } from 'src/debugger/service/debugger.service'; +import { IRequestApp } from 'src/utils/request/request.interface'; +import { + ERROR_CLASS_META_KEY, + ERROR_FUNCTION_META_KEY, +} from '../error.constant'; + +export class ErrorLogInterceptor implements NestInterceptor> { + constructor( + private readonly reflector: Reflector, + private readonly debuggerService: DebuggerService + ) {} + + async intercept( + context: ExecutionContext, + next: CallHandler + ): Promise | string>> { + const request: IRequestApp = context.switchToHttp().getRequest(); + const cls = this.reflector.get( + ERROR_CLASS_META_KEY, + context.getHandler() + ); + const func = this.reflector.get( + ERROR_FUNCTION_META_KEY, + context.getHandler() + ); + + if (context.getType() === 'http') { + return next.handle().pipe( + catchError((err) => { + if (err instanceof HttpException) { + this.debuggerService.error( + request.id, + { + description: err.message, + class: cls, + function: func, + }, + err.getResponse() + ); + } + + return throwError(() => err); + }) + ); + } + + return next.handle(); + } +} diff --git a/packages/auth-api/src/utils/file/file.constant.ts b/packages/auth-api/src/utils/file/file.constant.ts new file mode 100644 index 0000000..80e43a6 --- /dev/null +++ b/packages/auth-api/src/utils/file/file.constant.ts @@ -0,0 +1,16 @@ +export enum ENUM_FILE_IMAGE_MIME { + JPG = 'image/jpg', + JPEG = 'image/jpeg', + PNG = 'image/png', +} + +export enum ENUM_FILE_TYPE { + IMAGE = 'IMAGE', +} + +export enum ENUM_FILE_STATUS_CODE_ERROR { + FILE_NEEDED_ERROR = 5950, + FILE_MAX_SIZE_ERROR = 5951, + FILE_EXTENSION_ERROR = 5952, + FILE_MAX_ERROR = 5953, +} diff --git a/packages/auth-api/src/utils/file/file.decorator.ts b/packages/auth-api/src/utils/file/file.decorator.ts new file mode 100644 index 0000000..5419afa --- /dev/null +++ b/packages/auth-api/src/utils/file/file.decorator.ts @@ -0,0 +1,38 @@ +import { applyDecorators, UseInterceptors } from '@nestjs/common'; +import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express'; +import { ENUM_FILE_TYPE } from './file.constant'; +import { FileImageInterceptor } from './interceptor/file.image.interceptor'; + +export function UploadFileSingle( + field: string, + type: ENUM_FILE_TYPE, + required?: boolean +): any { + if (type === ENUM_FILE_TYPE.IMAGE) { + return applyDecorators( + UseInterceptors( + FileInterceptor(field), + FileImageInterceptor(required) + ) + ); + } + + return applyDecorators(UseInterceptors(FileInterceptor(field))); +} + +export function UploadFileMultiple( + field: string, + type: ENUM_FILE_TYPE, + required?: boolean +): any { + if (type === ENUM_FILE_TYPE.IMAGE) { + return applyDecorators( + UseInterceptors( + FilesInterceptor(field), + FileImageInterceptor(required) + ) + ); + } + + return applyDecorators(UseInterceptors(FilesInterceptor(field))); +} diff --git a/packages/auth-api/src/utils/file/file.interface.ts b/packages/auth-api/src/utils/file/file.interface.ts new file mode 100644 index 0000000..b32e6a6 --- /dev/null +++ b/packages/auth-api/src/utils/file/file.interface.ts @@ -0,0 +1 @@ +export type IFile = Express.Multer.File; diff --git a/packages/auth-api/src/utils/file/interceptor/file.image.interceptor.ts b/packages/auth-api/src/utils/file/interceptor/file.image.interceptor.ts new file mode 100644 index 0000000..2a52e91 --- /dev/null +++ b/packages/auth-api/src/utils/file/interceptor/file.image.interceptor.ts @@ -0,0 +1,100 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, + UnprocessableEntityException, + PayloadTooLargeException, + UnsupportedMediaTypeException, + Type, + mixin, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { ConfigService } from '@nestjs/config'; +import { + ENUM_FILE_IMAGE_MIME, + ENUM_FILE_STATUS_CODE_ERROR, +} from '../file.constant'; +import { IFile } from '../file.interface'; + +export function FileImageInterceptor( + required?: boolean +): Type { + @Injectable() + class MixinFileImageInterceptor implements NestInterceptor> { + constructor(private readonly configService: ConfigService) {} + + async intercept( + context: ExecutionContext, + next: CallHandler + ): Promise | string>> { + if (context.getType() === 'http') { + const ctx: HttpArgumentsHost = context.switchToHttp(); + const { file, files } = ctx.getRequest(); + + const finalFiles = files || file; + + if (Array.isArray(finalFiles)) { + const maxFiles = + this.configService.get('file.maxFiles'); + + if (required && finalFiles.length === 0) { + throw new UnprocessableEntityException({ + statusCode: + ENUM_FILE_STATUS_CODE_ERROR.FILE_NEEDED_ERROR, + message: 'file.error.notFound', + }); + } else if (finalFiles.length > maxFiles) { + throw new UnprocessableEntityException({ + statusCode: + ENUM_FILE_STATUS_CODE_ERROR.FILE_MAX_ERROR, + message: 'file.error.maxFiles', + }); + } + + for (const file of finalFiles) { + await this.validate(file); + } + } else { + await this.validate(finalFiles); + } + } + + return next.handle(); + } + + async validate(file: IFile): Promise { + if (required && !file) { + throw new UnprocessableEntityException({ + statusCode: ENUM_FILE_STATUS_CODE_ERROR.FILE_NEEDED_ERROR, + message: 'file.error.notFound', + }); + } else if (file) { + const { size, mimetype } = file; + + const maxSize = + this.configService.get('file.maxFileSize'); + if ( + !Object.values(ENUM_FILE_IMAGE_MIME).find( + (val) => val === mimetype.toLowerCase() + ) + ) { + throw new UnsupportedMediaTypeException({ + statusCode: + ENUM_FILE_STATUS_CODE_ERROR.FILE_EXTENSION_ERROR, + message: 'file.error.mimeInvalid', + }); + } else if (size > maxSize) { + throw new PayloadTooLargeException({ + statusCode: + ENUM_FILE_STATUS_CODE_ERROR.FILE_MAX_SIZE_ERROR, + message: 'file.error.maxSize', + }); + } + } + } + } + + return mixin(MixinFileImageInterceptor); +} diff --git a/packages/auth-api/src/utils/helper/helper.constant.ts b/packages/auth-api/src/utils/helper/helper.constant.ts new file mode 100644 index 0000000..f85059c --- /dev/null +++ b/packages/auth-api/src/utils/helper/helper.constant.ts @@ -0,0 +1,19 @@ +export enum ENUM_HELPER_DATE_FORMAT { + DATE = 'YYYY-MM-DD', + FRIENDLY_DATE = 'MMM, DD YYYY', + FRIENDLY_DATE_TIME = 'MMM, DD YYYY HH:MM:SS', + YEAR_MONTH = 'YYYY-MM', + MONTH_DATE = 'MM-DD', + ONLY_YEAR = 'YYYY', + ONLY_MONTH = 'MM', + ONLY_DATE = 'DD', + ISO_DATE = 'YYYY-MM-DDTHH:MM:SSZ', +} + +export enum ENUM_HELPER_DATE_DIFF { + MILIS = 'milis', + SECONDS = 'seconds', + HOURS = 'hours', + DAYS = 'days', + MINUTES = 'minutes', +} diff --git a/packages/auth-api/src/utils/helper/helper.interface.ts b/packages/auth-api/src/utils/helper/helper.interface.ts new file mode 100644 index 0000000..9cc3eb9 --- /dev/null +++ b/packages/auth-api/src/utils/helper/helper.interface.ts @@ -0,0 +1,51 @@ +import { + ENUM_HELPER_DATE_DIFF, + ENUM_HELPER_DATE_FORMAT, +} from './helper.constant'; + +export interface IHelperJwtOptions { + expiredIn: string; + notBefore?: string; + secretKey: string; +} + +export interface IHelperStringRandomOptions { + upperCase?: boolean; + safe?: boolean; + prefix?: string; +} + +export interface IHelperGeoCurrent { + latitude: number; + longitude: number; +} + +export interface IHelperGeoRules extends IHelperGeoCurrent { + radiusInMeters: number; +} + +export interface IHelperDateOptions { + timezone?: string; +} + +export interface IHelperDateOptionsDiff extends IHelperDateOptions { + format?: ENUM_HELPER_DATE_DIFF; +} + +export interface IHelperDateOptionsCreate extends IHelperDateOptions { + date?: string | number | Date; +} + +export interface IHelperDateOptionsFormat extends IHelperDateOptions { + format?: ENUM_HELPER_DATE_FORMAT | string; +} + +export interface IHelperDateOptionsForward extends IHelperDateOptions { + fromDate?: Date; +} + +export type IHelperDateOptionsBackward = IHelperDateOptionsForward; + +export interface IHelperDateOptionsMonth extends IHelperDateOptions { + year?: number; +} diff --git a/packages/auth-api/src/utils/helper/helper.module.ts b/packages/auth-api/src/utils/helper/helper.module.ts new file mode 100644 index 0000000..c15c0ed --- /dev/null +++ b/packages/auth-api/src/utils/helper/helper.module.ts @@ -0,0 +1,56 @@ +import { Global, Module } from '@nestjs/common'; +import { JwtModule } from '@nestjs/jwt'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { HelperService } from './service/helper.service'; +import { HelperArrayService } from './service/helper.array.service'; +import { HelperDateService } from './service/helper.date.service'; +import { HelperEncryptionService } from './service/helper.encryption.service'; +import { HelperHashService } from './service/helper.hash.service'; +import { HelperNumberService } from './service/helper.number.service'; +import { HelperStringService } from './service/helper.string.service'; +import { HelperFileService } from './service/helper.file.service'; +import { HelperGeoService } from './service/helper.geo.service'; + +@Global() +@Module({ + providers: [ + HelperService, + HelperArrayService, + HelperDateService, + HelperEncryptionService, + HelperHashService, + HelperNumberService, + HelperStringService, + HelperFileService, + HelperGeoService, + ], + exports: [ + HelperService, + HelperArrayService, + HelperDateService, + HelperEncryptionService, + HelperHashService, + HelperNumberService, + HelperStringService, + HelperFileService, + HelperGeoService, + ], + controllers: [], + imports: [ + JwtModule.registerAsync({ + inject: [ConfigService], + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + secret: configService.get( + 'helper.jwt.defaultSecretKey' + ), + signOptions: { + expiresIn: configService.get( + 'helper.jwt.defaultExpirationTime' + ), + }, + }), + }), + ], +}) +export class HelperModule {} diff --git a/packages/auth-api/src/utils/helper/service/helper.array.service.ts b/packages/auth-api/src/utils/helper/service/helper.array.service.ts new file mode 100644 index 0000000..3cb3fe3 --- /dev/null +++ b/packages/auth-api/src/utils/helper/service/helper.array.service.ts @@ -0,0 +1,107 @@ +import { Injectable } from '@nestjs/common'; +import _ from 'lodash'; + +@Injectable() +export class HelperArrayService { + getLeftByIndex(array: T[], index: number): T { + return _.nth(array, index); + } + + getRightByIndex(array: T[], index: number): T { + return _.nth(array, -Math.abs(index)); + } + + getLeftByLength(array: Array, length: number): Array { + return _.take(array, length); + } + + getRightByLength(array: Array, length: number): Array { + return _.takeRight(array, length); + } + + getLast(array: T[]): T { + return _.last(array); + } + + getFirst(array: T[]): T { + return _.head(array); + } + + getFirstIndexByValue(array: T[], value: T): number { + return _.indexOf(array, value); + } + + getLastIndexByValue(array: T[], value: T): number { + return _.lastIndexOf(array, value); + } + + removeByValue(array: T[], value: T): T[] { + return _.remove(array, function (n) { + return n === value; + }); + } + + removeLeftByLength(array: Array, length: number) { + return _.drop(array, length); + } + + removeRightByLength(array: Array, length: number) { + return _.dropRight(array, length); + } + + joinToString(array: Array, delimiter: string): string { + return _.join(array, delimiter); + } + + reverse(array: T[]): T[] { + return _.reverse(array); + } + + unique(array: T[]): T[] { + return _.uniq(array); + } + + shuffle(array: T[]): T[] { + return _.shuffle(array); + } + + merge(a: T[], b: T[]): T[] { + return _.concat(a, b); + } + + mergeUnique(a: T[], b: T[]): T[] { + return _.union(a, b); + } + + filterNotIncludeByValue(array: T[], value: T): T[] { + return _.without(array, value); + } + + filterNotIncludeByArray(a: T[], b: T[]): T[] { + return _.xor(a, b); + } + + filterIncludeByArray(a: T[], b: T[]): T[] { + return _.intersection(a, b); + } + + equals(a: T[], b: T[]): boolean { + return _.isEqual(a, b); + } + + notEquals(a: T[], b: T[]): boolean { + return !_.isEqual(a, b); + } + + in(a: T[], b: T[]): boolean { + return _.intersection(a, b).length > 0; + } + + notIn(a: T[], b: T[]): boolean { + return _.xor(a, b).length > 0; + } + + includes(a: T[], b: T): boolean { + return _.includes(a, b); + } +} diff --git a/packages/auth-api/src/utils/helper/service/helper.date.service.ts b/packages/auth-api/src/utils/helper/service/helper.date.service.ts new file mode 100644 index 0000000..f9b330d --- /dev/null +++ b/packages/auth-api/src/utils/helper/service/helper.date.service.ts @@ -0,0 +1,229 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import moment from 'moment-timezone'; +import { + ENUM_HELPER_DATE_DIFF, + ENUM_HELPER_DATE_FORMAT, +} from '../helper.constant'; +import { + IHelperDateOptions, + IHelperDateOptionsBackward, + IHelperDateOptionsCreate, + IHelperDateOptionsDiff, + IHelperDateOptionsFormat, + IHelperDateOptionsForward, + IHelperDateOptionsMonth, +} from '../helper.interface'; + +@Injectable() +export class HelperDateService { + private readonly timezone: string; + + constructor(private readonly configService: ConfigService) { + this.timezone = this.configService.get('app.timezone'); + } + + calculateAge(dateOfBirth: Date, options?: IHelperDateOptions): number { + return moment + .tz(options && options.timezone ? options.timezone : this.timezone) + .diff(dateOfBirth, 'years'); + } + + diff( + dateOne: Date, + dateTwo: Date, + options?: IHelperDateOptionsDiff + ): number { + const mDateOne = moment.tz( + dateOne, + options && options.timezone ? options.timezone : this.timezone + ); + const mDateTwo = moment.tz( + dateTwo, + options && options.timezone ? options.timezone : this.timezone + ); + const diff = moment.duration(mDateTwo.diff(mDateOne)); + + if (options && options.format === ENUM_HELPER_DATE_DIFF.MILIS) { + return diff.asMilliseconds(); + } else if ( + options && + options.format === ENUM_HELPER_DATE_DIFF.SECONDS + ) { + return diff.asSeconds(); + } else if (options && options.format === ENUM_HELPER_DATE_DIFF.HOURS) { + return diff.asHours(); + } else if ( + options && + options.format === ENUM_HELPER_DATE_DIFF.MINUTES + ) { + return diff.asMinutes(); + } else { + return diff.asDays(); + } + } + + check(date: string | Date | number, options?: IHelperDateOptions): boolean { + return moment(date, true) + .tz(options && options.timezone ? options.timezone : this.timezone) + .isValid(); + } + + checkTimezone(timezone: string): boolean { + return !!moment.tz.zone(timezone); + } + + create(options?: IHelperDateOptionsCreate): Date { + return moment + .tz( + options && options.date ? options.date : undefined, + options && options.timezone ? options.timezone : this.timezone + ) + .toDate(); + } + + timestamp(options?: IHelperDateOptionsCreate): number { + return moment + .tz( + options && options.date ? options.date : undefined, + options && options.timezone ? options.timezone : this.timezone + ) + .valueOf(); + } + + format(date: Date, options?: IHelperDateOptionsFormat): string { + return moment + .tz( + date, + options && options.timezone ? options.timezone : this.timezone + ) + .format( + options && options.format + ? options.format + : ENUM_HELPER_DATE_FORMAT.DATE + ); + } + + forwardInMinutes( + minutes: number, + options?: IHelperDateOptionsForward + ): Date { + return moment + .tz( + options && options.fromDate ? options.fromDate : undefined, + options && options.timezone ? options.timezone : this.timezone + ) + .add(minutes, 'm') + .toDate(); + } + + backwardInMinutes( + minutes: number, + options?: IHelperDateOptionsBackward + ): Date { + return moment + .tz( + options && options.fromDate ? options.fromDate : undefined, + options && options.timezone ? options.timezone : this.timezone + ) + .subtract(minutes, 'm') + .toDate(); + } + + forwardInDays(days: number, options?: IHelperDateOptionsForward): Date { + return moment + .tz( + options && options.fromDate ? options.fromDate : undefined, + options && options.timezone ? options.timezone : this.timezone + ) + .add(days, 'd') + .toDate(); + } + + backwardInDays(days: number, options?: IHelperDateOptionsBackward): Date { + return moment + .tz( + options && options.fromDate ? options.fromDate : undefined, + options && options.timezone ? options.timezone : this.timezone + ) + .subtract(days, 'd') + .toDate(); + } + + forwardInMonths(months: number, options?: IHelperDateOptionsForward): Date { + return moment + .tz( + options && options.fromDate ? options.fromDate : undefined, + options && options.timezone ? options.timezone : this.timezone + ) + .add(months, 'M') + .toDate(); + } + + backwardInMonths( + months: number, + options?: IHelperDateOptionsBackward + ): Date { + return moment + .tz( + options && options.fromDate ? options.fromDate : undefined, + options && options.timezone ? options.timezone : this.timezone + ) + .subtract(months, 'M') + .toDate(); + } + + endOfMonth(month: number, options?: IHelperDateOptionsMonth): Date { + const year = + options && options.year + ? options.year + : moment + .tz( + options && options.timezone + ? options.timezone + : this.timezone + ) + .year(); + return moment + .tz(options && options.timezone ? options.timezone : this.timezone) + .year(year) + .month(month - 1) + .endOf('month') + .toDate(); + } + + startOfMonth(month: number, options?: IHelperDateOptionsMonth): Date { + const year = + options && options.year + ? options.year + : moment + .tz( + options && options.timezone + ? options.timezone + : this.timezone + ) + .year(); + return moment + .tz(options && options.timezone ? options.timezone : this.timezone) + .year(year) + .month(month - 1) + .startOf('month') + .toDate(); + } + + endOfYear(year: number, options?: IHelperDateOptions): Date { + return moment + .tz(options && options.timezone ? options.timezone : this.timezone) + .year(year) + .endOf('year') + .toDate(); + } + + startOfYear(year: number, options?: IHelperDateOptions): Date { + return moment + .tz(options && options.timezone ? options.timezone : this.timezone) + .year(year) + .startOf('year') + .toDate(); + } +} diff --git a/packages/auth-api/src/utils/helper/service/helper.encryption.service.ts b/packages/auth-api/src/utils/helper/service/helper.encryption.service.ts new file mode 100644 index 0000000..dc1d683 --- /dev/null +++ b/packages/auth-api/src/utils/helper/service/helper.encryption.service.ts @@ -0,0 +1,75 @@ +import { Injectable } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { AES, enc, mode, pad } from 'crypto-js'; +import { IHelperJwtOptions } from '../helper.interface'; + +@Injectable() +export class HelperEncryptionService { + constructor(private readonly jwtService: JwtService) {} + + base64Encrypt(data: string): string { + const buff: Buffer = Buffer.from(data, 'utf8'); + return buff.toString('base64'); + } + + base64Decrypt(data: string): string { + const buff: Buffer = Buffer.from(data, 'base64'); + return buff.toString('utf8'); + } + + base64Compare(clientBasicToken: string, ourBasicToken: string): boolean { + return ourBasicToken === clientBasicToken; + } + + aes256Encrypt( + data: string | Record | Record[], + key: string, + iv: string + ): string { + const cIv = enc.Utf8.parse(iv); + const cipher = AES.encrypt(JSON.stringify(data), key, { + mode: mode.CBC, + padding: pad.Pkcs7, + iv: cIv, + }); + + return cipher.toString(); + } + + aes256Decrypt(encrypted: string, key: string, iv: string): string { + const cIv = enc.Utf8.parse(iv); + const cipher = AES.decrypt(encrypted, key, { + mode: mode.CBC, + padding: pad.Pkcs7, + iv: cIv, + }); + + return cipher.toString(enc.Utf8); + } + + jwtEncrypt( + payload: Record, + options: IHelperJwtOptions + ): string { + return this.jwtService.sign(payload, { + secret: options.secretKey, + expiresIn: options.expiredIn, + notBefore: options.notBefore || 0, + }); + } + + jwtDecrypt(token: string): Record { + return this.jwtService.decode(token) as Record; + } + + jwtVerify(token: string, options?: IHelperJwtOptions): boolean { + try { + this.jwtService.verify(token, { + secret: options.secretKey, + }); + return true; + } catch (e) { + return false; + } + } +} diff --git a/packages/auth-api/src/utils/helper/service/helper.file.service.ts b/packages/auth-api/src/utils/helper/service/helper.file.service.ts new file mode 100644 index 0000000..26c965b --- /dev/null +++ b/packages/auth-api/src/utils/helper/service/helper.file.service.ts @@ -0,0 +1,53 @@ +/* istanbul ignore file */ + +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import excelJs from 'exceljs'; +import { HelperDateService } from './helper.date.service'; + +@Injectable() +export class HelperFileService { + private readonly appName: string; + + constructor( + private readonly configService: ConfigService, + private readonly helperDateService: HelperDateService + ) { + this.appName = this.configService.get('app.name'); + } + + async writeExcel( + headers: string[], + rows: Record[] + ): Promise { + const workbook = new excelJs.Workbook(); + workbook.creator = this.appName; + workbook.lastModifiedBy = this.appName; + workbook.created = this.helperDateService.create(); + workbook.modified = this.helperDateService.create(); + workbook.properties.date1904 = true; + workbook.views = [ + { + x: 0, + y: 0, + width: 10000, + height: 20000, + firstSheet: 0, + activeTab: 1, + visibility: 'visible', + }, + ]; + + // sheet + const worksheet = workbook.addWorksheet('Sheet 1', { + views: [{ state: 'frozen', xSplit: 1 }, { showGridLines: true }], + }); + + worksheet.columns = headers.map((val) => ({ + header: val, + })); + worksheet.addRows(rows); + + return (await workbook.xlsx.writeBuffer()) as Buffer; + } +} diff --git a/packages/auth-api/src/utils/helper/service/helper.geo.service.ts b/packages/auth-api/src/utils/helper/service/helper.geo.service.ts new file mode 100644 index 0000000..4709ef3 --- /dev/null +++ b/packages/auth-api/src/utils/helper/service/helper.geo.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@nestjs/common'; +import { isPointWithinRadius } from 'geolib'; +import { IHelperGeoCurrent, IHelperGeoRules } from '../helper.interface'; + +@Injectable() +export class HelperGeoService { + inRadius(geoRule: IHelperGeoRules, geoCurrent: IHelperGeoCurrent): boolean { + return isPointWithinRadius( + { latitude: geoRule.latitude, longitude: geoRule.longitude }, + geoCurrent, + geoRule.radiusInMeters + ); + } +} diff --git a/packages/auth-api/src/utils/helper/service/helper.hash.service.ts b/packages/auth-api/src/utils/helper/service/helper.hash.service.ts new file mode 100644 index 0000000..7681979 --- /dev/null +++ b/packages/auth-api/src/utils/helper/service/helper.hash.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { compareSync, genSaltSync, hashSync } from 'bcrypt'; +import { SHA256, enc } from 'crypto-js'; + +@Injectable() +export class HelperHashService { + randomSalt(length: number): string { + return genSaltSync(length); + } + + bcrypt(passwordString: string, salt: string): string { + return hashSync(passwordString, salt); + } + + bcryptCompare(passwordString: string, passwordHashed: string): boolean { + return compareSync(passwordString, passwordHashed); + } + + sha256(string: string): string { + return SHA256(string).toString(enc.Hex); + } + + sha256Compare(hashOne: string, hashTwo: string): boolean { + return hashOne === hashTwo; + } +} diff --git a/packages/auth-api/src/utils/helper/service/helper.number.service.ts b/packages/auth-api/src/utils/helper/service/helper.number.service.ts new file mode 100644 index 0000000..a2c3932 --- /dev/null +++ b/packages/auth-api/src/utils/helper/service/helper.number.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { faker } from '@faker-js/faker'; + +@Injectable() +export class HelperNumberService { + check(number: string): boolean { + const regex = /^-?\d+$/; + return regex.test(number); + } + + convert(number: string): number { + return Number(number); + } + + random(length: number): number { + const min: number = Number.parseInt(`1`.padEnd(length, '0')); + const max: number = Number.parseInt(`9`.padEnd(length, '9')); + return this.randomInRange(min, max); + } + + randomInRange(min: number, max: number): number { + return faker.datatype.number({ min, max }); + } +} diff --git a/packages/auth-api/src/utils/helper/service/helper.service.ts b/packages/auth-api/src/utils/helper/service/helper.service.ts new file mode 100644 index 0000000..1b132e0 --- /dev/null +++ b/packages/auth-api/src/utils/helper/service/helper.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class HelperService { + async delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } +} diff --git a/packages/auth-api/src/utils/helper/service/helper.string.service.ts b/packages/auth-api/src/utils/helper/service/helper.string.service.ts new file mode 100644 index 0000000..5d8b5ea --- /dev/null +++ b/packages/auth-api/src/utils/helper/service/helper.string.service.ts @@ -0,0 +1,109 @@ +import { Injectable } from '@nestjs/common'; +import { faker } from '@faker-js/faker'; +import { IHelperStringRandomOptions } from '../helper.interface'; +import { HelperDateService } from './helper.date.service'; + +@Injectable() +export class HelperStringService { + constructor(private readonly helperDateService: HelperDateService) {} + + checkEmail(email: string): boolean { + const regex = /\S+@\S+\.\S+/; + return regex.test(email); + } + + randomReference(length: number, prefix?: string): string { + const timestamp = `${this.helperDateService.timestamp()}`; + const randomString: string = this.random(length, { + safe: true, + upperCase: true, + }); + + return prefix + ? `${prefix}-${timestamp}${randomString}` + : `${timestamp}${randomString}`; + } + + random(length: number, options?: IHelperStringRandomOptions): string { + const rString = + options && options.safe + ? faker.internet.password( + length, + true, + /[A-Z]/, + options && options.prefix ? options.prefix : undefined + ) + : faker.internet.password( + length, + false, + /\w/, + options && options.prefix ? options.prefix : undefined + ); + + return options && options.upperCase ? rString.toUpperCase() : rString; + } + + censor(value: string): string { + const length = value.length; + if (length === 1) { + return value; + } + + const end = length > 4 ? length - 4 : 1; + const censorString = '*'.repeat(end > 10 ? 10 : end); + const visibleString = value.substring(end, length); + return `${censorString}${visibleString}`; + } + + checkStringOrNumber(text: string) { + const regex = new RegExp(/^[\w.-]+$/); + + return regex.test(text); + } + + convertStringToNumberOrBooleanIfPossible( + text: string + ): string | number | boolean { + let convertValue: string | boolean | number = text; + + const regexNumber = /^-?\d+$/; + if (text === 'true' || text === 'false') { + convertValue = text === 'true'; + } else if (regexNumber.test(text)) { + convertValue = Number(text); + } + + return convertValue; + } + + checkPasswordWeak(password: string, length?: number): boolean { + const regex = new RegExp( + `^(?=.*?[A-Z])(?=.*?[a-z]).{${length || 8},}$` + ); + + return regex.test(password); + } + + checkPasswordMedium(password: string, length?: number): boolean { + const regex = new RegExp( + `^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{${length || 8},}$` + ); + + return regex.test(password); + } + + checkPasswordStrong(password: string, length?: number): boolean { + const regex = new RegExp( + `^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{${ + length || 8 + },}$` + ); + + return regex.test(password); + } + + checkSafeString(text: string): boolean { + const regex = new RegExp('^[A-Za-z0-9_-]+$'); + return regex.test(text); + } +} diff --git a/packages/auth-api/src/utils/middleware/body-parser/body-parser.middleware.ts b/packages/auth-api/src/utils/middleware/body-parser/body-parser.middleware.ts new file mode 100644 index 0000000..1c8e618 --- /dev/null +++ b/packages/auth-api/src/utils/middleware/body-parser/body-parser.middleware.ts @@ -0,0 +1,38 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; +import bodyParser from 'body-parser'; + +@Injectable() +export class UrlencodedBodyParserMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction): void { + bodyParser.urlencoded({ extended: false })(req, res, next); + } +} + +@Injectable() +export class JsonBodyParserMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction): void { + bodyParser.json()(req, res, next); + } +} + +@Injectable() +export class RawBodyParserMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction): void { + bodyParser.raw()(req, res, next); + } +} + +@Injectable() +export class TextBodyParserMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction): void { + bodyParser.text()(req, res, next); + } +} + +@Injectable() +export class HtmlBodyParserMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction): void { + bodyParser.raw({ type: 'text/html' })(req, res, next); + } +} diff --git a/packages/auth-api/src/utils/middleware/compression/compression.middleware.ts b/packages/auth-api/src/utils/middleware/compression/compression.middleware.ts new file mode 100644 index 0000000..fcf0c1f --- /dev/null +++ b/packages/auth-api/src/utils/middleware/compression/compression.middleware.ts @@ -0,0 +1,10 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; +import compression from 'compression'; + +@Injectable() +export class CompressionMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction): void { + compression()(req, res, next); + } +} diff --git a/packages/auth-api/src/utils/middleware/cors/cors.middleware.ts b/packages/auth-api/src/utils/middleware/cors/cors.middleware.ts new file mode 100644 index 0000000..9559001 --- /dev/null +++ b/packages/auth-api/src/utils/middleware/cors/cors.middleware.ts @@ -0,0 +1,37 @@ +import { HttpStatus, Injectable, NestMiddleware } from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; +import cors, { CorsOptions } from 'cors'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class CorsMiddleware implements NestMiddleware { + constructor(private readonly configService: ConfigService) {} + + use(req: Request, res: Response, next: NextFunction): void { + const mode: string = this.configService.get('app.mode'); + + const allowOrigin = + mode === 'secure' + ? this.configService.get( + 'middleware.cors.allowOrigin' + ) + : '*'; + const allowMethod = this.configService.get( + 'middleware.cors.allowMethod' + ); + const allowHeader = this.configService.get( + 'middleware.cors.allowHeader' + ); + + const corsOptions: CorsOptions = { + origin: allowOrigin, + methods: allowMethod, + allowedHeaders: allowHeader, + preflightContinue: false, + credentials: true, + optionsSuccessStatus: HttpStatus.NO_CONTENT, + }; + + cors(corsOptions)(req, res, next); + } +} diff --git a/packages/auth-api/src/utils/middleware/custom-language/custom-language.middleware.ts b/packages/auth-api/src/utils/middleware/custom-language/custom-language.middleware.ts new file mode 100644 index 0000000..ad91651 --- /dev/null +++ b/packages/auth-api/src/utils/middleware/custom-language/custom-language.middleware.ts @@ -0,0 +1,45 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { Response, NextFunction } from 'express'; +import { MessageService } from 'src/message/service/message.service'; +import { HelperArrayService } from 'src/utils/helper/service/helper.array.service'; +import { IRequestApp } from 'src/utils/request/request.interface'; + +@Injectable() +export class CustomLanguageMiddleware implements NestMiddleware { + constructor( + private readonly messageService: MessageService, + private readonly helperArrayService: HelperArrayService, + private readonly configService: ConfigService + ) {} + + async use( + req: IRequestApp, + res: Response, + next: NextFunction + ): Promise { + let language: string = this.configService.get('app.language'); + const reqLanguages: string = req.headers['x-custom-lang'] as string; + const enumLanguage: string[] = Object.values( + await this.messageService.getLanguages() + ); + if (reqLanguages) { + const languages: string[] = this.helperArrayService.unique( + reqLanguages + .split(',') + .filter((val) => + this.helperArrayService.includes(enumLanguage, val) + ) + ); + + if (languages.length > 0) { + language = languages.join(','); + } + } + + req.headers['x-custom-lang'] = language; + req.customLang = language; + + next(); + } +} diff --git a/packages/auth-api/src/utils/middleware/helmet/helmet.middleware.ts b/packages/auth-api/src/utils/middleware/helmet/helmet.middleware.ts new file mode 100644 index 0000000..67935ee --- /dev/null +++ b/packages/auth-api/src/utils/middleware/helmet/helmet.middleware.ts @@ -0,0 +1,10 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; +import helmet from 'helmet'; + +@Injectable() +export class HelmetMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction): void { + helmet()(req, res, next); + } +} diff --git a/packages/auth-api/src/utils/middleware/http-debugger/http-debugger.constant.ts b/packages/auth-api/src/utils/middleware/http-debugger/http-debugger.constant.ts new file mode 100644 index 0000000..37c33f7 --- /dev/null +++ b/packages/auth-api/src/utils/middleware/http-debugger/http-debugger.constant.ts @@ -0,0 +1,3 @@ +export const DEBUGGER_HTTP_FORMAT = + "':remote-addr' - ':remote-user' - '[:date[iso]]' - 'HTTP/:http-version' - '[:status]' - ':method' - ':url' - 'Request Header :: :req-headers' - 'Request Params :: :req-params' - 'Request Body :: :req-body' - 'Response Header :: :res[header]' - 'Response Body :: :res-body' - ':response-time ms' - ':referrer' - ':user-agent'"; +export const DEBUGGER_HTTP_NAME = 'http'; diff --git a/packages/auth-api/src/utils/middleware/http-debugger/http-debugger.interface.ts b/packages/auth-api/src/utils/middleware/http-debugger/http-debugger.interface.ts new file mode 100644 index 0000000..dc9fbd2 --- /dev/null +++ b/packages/auth-api/src/utils/middleware/http-debugger/http-debugger.interface.ts @@ -0,0 +1,15 @@ +import { RotatingFileStream } from 'rotating-file-stream'; +import { Response } from 'express'; + +export interface IHttpDebuggerConfigOptions { + readonly stream: RotatingFileStream; +} + +export interface IHttpDebuggerConfig { + readonly debuggerHttpFormat: string; + readonly HttpDebuggerOptions: IHttpDebuggerConfigOptions; +} + +export interface ICustomResponse extends Response { + body: string; +} diff --git a/packages/auth-api/src/utils/middleware/http-debugger/http-debugger.middleware.ts b/packages/auth-api/src/utils/middleware/http-debugger/http-debugger.middleware.ts new file mode 100644 index 0000000..689e08f --- /dev/null +++ b/packages/auth-api/src/utils/middleware/http-debugger/http-debugger.middleware.ts @@ -0,0 +1,99 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import morgan from 'morgan'; +import { Request, Response, NextFunction } from 'express'; +import { createStream } from 'rotating-file-stream'; +import { ConfigService } from '@nestjs/config'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { + ICustomResponse, + IHttpDebuggerConfig, + IHttpDebuggerConfigOptions, +} from './http-debugger.interface'; +import { + DEBUGGER_HTTP_FORMAT, + DEBUGGER_HTTP_NAME, +} from './http-debugger.constant'; + +@Injectable() +export class HttpDebuggerMiddleware implements NestMiddleware { + private readonly maxSize: string; + private readonly maxFiles: number; + + constructor( + private readonly configService: ConfigService, + private readonly helperDateService: HelperDateService + ) { + this.maxSize = this.configService.get( + 'app.debugger.http.maxSize' + ); + this.maxFiles = this.configService.get( + 'app.debugger.http.maxFiles' + ); + } + + private customToken(): void { + morgan.token('req-params', (req: Request) => + JSON.stringify(req.params) + ); + + morgan.token('req-body', (req: Request) => JSON.stringify(req.body)); + + morgan.token( + 'res-body', + (req: Request, res: ICustomResponse) => res.body + ); + + morgan.token('req-headers', (req: Request) => + JSON.stringify(req.headers) + ); + } + + private async httpLogger(): Promise { + const date: string = this.helperDateService.format( + this.helperDateService.create() + ); + const HttpDebuggerOptions: IHttpDebuggerConfigOptions = { + stream: createStream(`${date}.log`, { + path: `./logs/${DEBUGGER_HTTP_NAME}/`, + maxSize: this.maxSize, + maxFiles: this.maxFiles, + compress: true, + interval: '1d', + }), + }; + + return { + debuggerHttpFormat: DEBUGGER_HTTP_FORMAT, + HttpDebuggerOptions, + }; + } + + async use(req: Request, res: Response, next: NextFunction): Promise { + const config: IHttpDebuggerConfig = await this.httpLogger(); + this.customToken(); + morgan(config.debuggerHttpFormat, config.HttpDebuggerOptions)( + req, + res, + next + ); + } +} + +@Injectable() +export class HttpDebuggerResponseMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction): void { + const send: any = res.send; + const resOld: any = res; + + // Add response data to response + // this is for morgan + resOld.send = (body: any) => { + resOld.body = body; + resOld.send = send; + resOld.send(body); + res = resOld as Response; + }; + + next(); + } +} diff --git a/packages/auth-api/src/utils/middleware/maintenance/maintenance.middleware.ts b/packages/auth-api/src/utils/middleware/maintenance/maintenance.middleware.ts new file mode 100644 index 0000000..afb1c1f --- /dev/null +++ b/packages/auth-api/src/utils/middleware/maintenance/maintenance.middleware.ts @@ -0,0 +1,33 @@ +import { + Injectable, + NestMiddleware, + ServiceUnavailableException, +} from '@nestjs/common'; +import { Response, NextFunction } from 'express'; +import { SettingDocument } from 'src/setting/schema/setting.schema'; +import { SettingService } from 'src/setting/service/setting.service'; +import { ENUM_STATUS_CODE_ERROR } from 'src/utils/error/error.constant'; +import { IRequestApp } from 'src/utils/request/request.interface'; + +@Injectable() +export class MaintenanceMiddleware implements NestMiddleware { + constructor(private readonly settingService: SettingService) {} + + async use( + req: IRequestApp, + res: Response, + next: NextFunction + ): Promise { + const maintenance: SettingDocument = + await this.settingService.findOneByName('maintenance'); + + if (maintenance.value as boolean) { + throw new ServiceUnavailableException({ + statusCode: ENUM_STATUS_CODE_ERROR.SERVICE_UNAVAILABLE, + message: 'http.serverError.serviceUnavailable', + }); + } + + next(); + } +} diff --git a/packages/auth-api/src/utils/middleware/middleware.module.ts b/packages/auth-api/src/utils/middleware/middleware.module.ts new file mode 100644 index 0000000..dcb2474 --- /dev/null +++ b/packages/auth-api/src/utils/middleware/middleware.module.ts @@ -0,0 +1,85 @@ +import { + Module, + NestModule, + MiddlewareConsumer, + RequestMethod, +} from '@nestjs/common'; +import { CorsMiddleware } from './cors/cors.middleware'; +import { + HtmlBodyParserMiddleware, + JsonBodyParserMiddleware, + RawBodyParserMiddleware, + TextBodyParserMiddleware, + UrlencodedBodyParserMiddleware, +} from './body-parser/body-parser.middleware'; +import { + HttpDebuggerMiddleware, + HttpDebuggerResponseMiddleware, +} from './http-debugger/http-debugger.middleware'; +import { HelmetMiddleware } from './helmet/helmet.middleware'; +import { RateLimitMiddleware } from './rate-limit/rate-limit.middleware'; +import { UserAgentMiddleware } from './user-agent/user-agent.middleware'; +import { CompressionMiddleware } from './compression/compression.middleware'; +import { MaintenanceMiddleware } from './maintenance/maintenance.middleware'; +import { RequestIdMiddleware } from './request-id/request-id.middleware'; +import { TimezoneMiddleware } from './timezone/timezone.middleware'; +import { CustomLanguageMiddleware } from './custom-language/custom-language.middleware'; +import { ResponseTimeMiddleware } from './response-time/response-time.middleware'; +import { TimestampMiddleware } from './timestamp/timestamp.middleware'; + +@Module({}) +export class MiddlewareModule implements NestModule { + configure(consumer: MiddlewareConsumer): void { + consumer + .apply( + RequestIdMiddleware, + TimezoneMiddleware, + JsonBodyParserMiddleware, + RawBodyParserMiddleware, + HtmlBodyParserMiddleware, + TextBodyParserMiddleware, + UrlencodedBodyParserMiddleware, + CompressionMiddleware, + CorsMiddleware, + HttpDebuggerResponseMiddleware, + HttpDebuggerMiddleware, + HelmetMiddleware, + RateLimitMiddleware, + UserAgentMiddleware, + CustomLanguageMiddleware, + ResponseTimeMiddleware, + TimestampMiddleware + ) + .forRoutes('*'); + + consumer + .apply(MaintenanceMiddleware) + .exclude( + { + path: 'api/v:version*/auth/login', + method: RequestMethod.POST, + }, + { + path: 'api/auth/login', + method: RequestMethod.POST, + }, + { + path: 'api/v:version*/auth/refresh', + method: RequestMethod.POST, + }, + { + path: 'api/auth/refresh', + method: RequestMethod.POST, + }, + { + path: 'api/v:version*/admin/setting/(.*)', + method: RequestMethod.ALL, + }, + { + path: 'api/admin/setting/(.*)', + method: RequestMethod.ALL, + } + ) + .forRoutes('*'); + } +} diff --git a/packages/auth-api/src/utils/middleware/rate-limit/rate-limit.constant.ts b/packages/auth-api/src/utils/middleware/rate-limit/rate-limit.constant.ts new file mode 100644 index 0000000..c4440a1 --- /dev/null +++ b/packages/auth-api/src/utils/middleware/rate-limit/rate-limit.constant.ts @@ -0,0 +1,2 @@ +export const RESET_TIME = 1 * 500; // 0.5s +export const MAX_REQUEST_PER_IP = 1; // limit each IP to 1 requests per windowMs diff --git a/packages/auth-api/src/utils/middleware/rate-limit/rate-limit.middleware.ts b/packages/auth-api/src/utils/middleware/rate-limit/rate-limit.middleware.ts new file mode 100644 index 0000000..0a0d6a8 --- /dev/null +++ b/packages/auth-api/src/utils/middleware/rate-limit/rate-limit.middleware.ts @@ -0,0 +1,24 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { Request, Response, NextFunction } from 'express'; +import rateLimit from 'express-rate-limit'; +import { MAX_REQUEST_PER_IP, RESET_TIME } from './rate-limit.constant'; + +@Injectable() +export class RateLimitMiddleware implements NestMiddleware { + constructor(private readonly configService: ConfigService) {} + + use(req: Request, res: Response, next: NextFunction): void { + const resetTime = this.configService.get( + 'middleware.rateLimit.resetTime' + ); + const maxRequestPerId = this.configService.get( + 'middleware.rateLimit.maxRequestPerId' + ); + + rateLimit({ + windowMs: resetTime || RESET_TIME, + max: maxRequestPerId || MAX_REQUEST_PER_IP, + })(req, res, next); + } +} diff --git a/packages/auth-api/src/utils/middleware/request-id/request-id.middleware.ts b/packages/auth-api/src/utils/middleware/request-id/request-id.middleware.ts new file mode 100644 index 0000000..df383bb --- /dev/null +++ b/packages/auth-api/src/utils/middleware/request-id/request-id.middleware.ts @@ -0,0 +1,18 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { Response, NextFunction } from 'express'; +import { IRequestApp } from 'src/utils/request/request.interface'; +import { v4 } from 'uuid'; + +@Injectable() +export class RequestIdMiddleware implements NestMiddleware { + async use( + req: IRequestApp, + res: Response, + next: NextFunction + ): Promise { + const uuid: string = v4(); + req.headers['x-request-id'] = uuid; + req.id = uuid; + next(); + } +} diff --git a/packages/auth-api/src/utils/middleware/response-time/response-time.middleware.ts b/packages/auth-api/src/utils/middleware/response-time/response-time.middleware.ts new file mode 100644 index 0000000..185f579 --- /dev/null +++ b/packages/auth-api/src/utils/middleware/response-time/response-time.middleware.ts @@ -0,0 +1,10 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; +import responseTime from 'response-time'; + +@Injectable() +export class ResponseTimeMiddleware implements NestMiddleware { + async use(req: Request, res: Response, next: NextFunction): Promise { + responseTime()(req, res, next); + } +} diff --git a/packages/auth-api/src/utils/middleware/timestamp/timestamp.middleware.ts b/packages/auth-api/src/utils/middleware/timestamp/timestamp.middleware.ts new file mode 100644 index 0000000..a118d9b --- /dev/null +++ b/packages/auth-api/src/utils/middleware/timestamp/timestamp.middleware.ts @@ -0,0 +1,33 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { Response, NextFunction } from 'express'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { IRequestApp } from 'src/utils/request/request.interface'; + +@Injectable() +export class TimestampMiddleware implements NestMiddleware { + constructor( + private readonly helperDateService: HelperDateService, + private readonly configService: ConfigService + ) {} + + async use( + req: IRequestApp, + res: Response, + next: NextFunction + ): Promise { + const mode: string = this.configService.get('app.mode'); + let reqTs: string = req.headers['x-timestamp'] as string; + + const currentTimestamp: number = this.helperDateService.timestamp(); + + if (mode !== 'secure' && !reqTs) { + reqTs = `${currentTimestamp}`; + } + + req.headers['x-timestamp'] = reqTs; + req.timestamp = reqTs; + + next(); + } +} diff --git a/packages/auth-api/src/utils/middleware/timezone/timezone.middleware.ts b/packages/auth-api/src/utils/middleware/timezone/timezone.middleware.ts new file mode 100644 index 0000000..5e4099b --- /dev/null +++ b/packages/auth-api/src/utils/middleware/timezone/timezone.middleware.ts @@ -0,0 +1,31 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { Response, NextFunction } from 'express'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { IRequestApp } from 'src/utils/request/request.interface'; + +@Injectable() +export class TimezoneMiddleware implements NestMiddleware { + constructor( + private readonly helperDateService: HelperDateService, + private readonly configService: ConfigService + ) {} + + async use( + req: IRequestApp, + res: Response, + next: NextFunction + ): Promise { + const tz: string = this.configService.get('app.timezone'); + const reqTz: string = req.headers['x-timezone'] as string; + + if (!reqTz || (reqTz && !this.helperDateService.checkTimezone(reqTz))) { + req.headers['x-timezone'] = tz; + req.timezone = tz; + } else { + req.timezone = reqTz; + } + + next(); + } +} diff --git a/packages/auth-api/src/utils/middleware/user-agent/user-agent.middleware.ts b/packages/auth-api/src/utils/middleware/user-agent/user-agent.middleware.ts new file mode 100644 index 0000000..ca908ec --- /dev/null +++ b/packages/auth-api/src/utils/middleware/user-agent/user-agent.middleware.ts @@ -0,0 +1,29 @@ +import { ForbiddenException, Injectable, NestMiddleware } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { Response, NextFunction } from 'express'; +import { ENUM_REQUEST_STATUS_CODE_ERROR } from 'src/utils/request/request.constant'; +import { IRequestApp } from 'src/utils/request/request.interface'; +import userAgentParser from 'ua-parser-js'; + +@Injectable() +export class UserAgentMiddleware implements NestMiddleware { + constructor(private readonly configService: ConfigService) {} + + use(req: IRequestApp, res: Response, next: NextFunction): void { + const mode: string = this.configService.get('app.mode'); + + if (mode === 'secure') { + // Put your specific user agent + const userAgent: string = req.headers['user-agent'] as string; + if (!userAgent) { + throw new ForbiddenException({ + statusCode: + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_USER_AGENT_INVALID_ERROR, + message: 'middleware.error.userAgentInvalid', + }); + } + } + req.userAgent = userAgentParser(req.headers['user-agent']); + next(); + } +} diff --git a/packages/auth-api/src/utils/request/guard/request.param.guard.ts b/packages/auth-api/src/utils/request/guard/request.param.guard.ts new file mode 100644 index 0000000..9109c0d --- /dev/null +++ b/packages/auth-api/src/utils/request/guard/request.param.guard.ts @@ -0,0 +1,40 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + Type, + mixin, + BadRequestException, +} from '@nestjs/common'; +import { ClassConstructor, plainToInstance } from 'class-transformer'; +import { validate, ValidationError } from 'class-validator'; +import { ENUM_REQUEST_STATUS_CODE_ERROR } from '../request.constant'; + +export function ParamGuard( + classValidation: ClassConstructor[] +): Type { + @Injectable() + class MixinParamGuard implements CanActivate { + async canActivate(context: ExecutionContext): Promise { + const { params } = context.switchToHttp().getRequest(); + for (const cv of classValidation) { + const request = plainToInstance(cv, params); + + const errors: ValidationError[] = await validate(request); + + if (errors.length > 0) { + throw new BadRequestException({ + statusCode: + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_VALIDATION_ERROR, + message: 'http.clientError.badRequest', + errors: errors, + }); + } + } + + return true; + } + } + + return mixin(MixinParamGuard); +} diff --git a/packages/auth-api/src/utils/request/interceptor/request.timestamp.interceptor.ts b/packages/auth-api/src/utils/request/interceptor/request.timestamp.interceptor.ts new file mode 100644 index 0000000..1db3b04 --- /dev/null +++ b/packages/auth-api/src/utils/request/interceptor/request.timestamp.interceptor.ts @@ -0,0 +1,85 @@ +import { + CallHandler, + ExecutionContext, + ForbiddenException, + NestInterceptor, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { Reflector } from '@nestjs/core'; +import { Observable } from 'rxjs'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; +import { + ENUM_REQUEST_STATUS_CODE_ERROR, + REQUEST_EXCLUDE_TIMESTAMP_META_KEY, +} from '../request.constant'; +import { IRequestApp } from '../request.interface'; + +export class RequestTimestampInterceptor + implements NestInterceptor> +{ + constructor( + private readonly configService: ConfigService, + private readonly reflector: Reflector, + private readonly helperDateService: HelperDateService + ) {} + + async intercept( + context: ExecutionContext, + next: CallHandler + ): Promise | string>> { + if (context.getType() === 'http') { + const request: IRequestApp = context.switchToHttp().getRequest(); + const { headers } = request; + const mode: string = this.configService.get('app.mode'); + const reqTs: string = headers['x-timestamp'] as string; + const currentTimestamp: number = this.helperDateService.timestamp(); + const excludeTimestamp = this.reflector.get( + REQUEST_EXCLUDE_TIMESTAMP_META_KEY, + context.getHandler() + ); + + if (!excludeTimestamp && mode === 'secure') { + const toleranceTimeInMinutes = this.configService.get( + 'middleware.timestamp.toleranceTimeInMinutes' + ); + const check: boolean = this.helperDateService.check( + Number.isNaN(Number.parseInt(reqTs)) + ? reqTs + : Number.parseInt(reqTs) + ); + if (!reqTs || !check) { + throw new ForbiddenException({ + statusCode: + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_TIMESTAMP_INVALID_ERROR, + message: 'middleware.error.timestampInvalid', + }); + } + + const timestamp = this.helperDateService.create({ + date: Number.isNaN(Number.parseInt(reqTs)) + ? reqTs + : Number.parseInt(reqTs), + }); + const toleranceMin = this.helperDateService.backwardInMinutes( + toleranceTimeInMinutes + ); + const toleranceMax = this.helperDateService.forwardInMinutes( + toleranceTimeInMinutes + ); + if (timestamp < toleranceMin || timestamp > toleranceMax) { + throw new ForbiddenException({ + statusCode: + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_TIMESTAMP_INVALID_ERROR, + message: 'middleware.error.timestampInvalid', + }); + } + } else { + const newTimestamp = reqTs || `${currentTimestamp}`; + request.headers['x-timestamp'] = newTimestamp; + request.timestamp = newTimestamp; + } + } + + return next.handle(); + } +} diff --git a/packages/auth-api/src/utils/request/pipe/request.add-date.pipe.ts b/packages/auth-api/src/utils/request/pipe/request.add-date.pipe.ts new file mode 100644 index 0000000..91e5104 --- /dev/null +++ b/packages/auth-api/src/utils/request/pipe/request.add-date.pipe.ts @@ -0,0 +1,20 @@ +import { Injectable, mixin, PipeTransform, Type } from '@nestjs/common'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; + +export function RequestAddDatePipe(days: number): Type { + @Injectable() + class MixinRequestAddDatePipe implements PipeTransform { + constructor(private readonly helperDateService: HelperDateService) {} + + async transform(value: any) { + // no need timezone because convert a date + return this.helperDateService.forwardInDays(days, { + fromDate: this.helperDateService.create({ + date: value, + }), + }); + } + } + + return mixin(MixinRequestAddDatePipe); +} diff --git a/packages/auth-api/src/utils/request/request.constant.ts b/packages/auth-api/src/utils/request/request.constant.ts new file mode 100644 index 0000000..cebecad --- /dev/null +++ b/packages/auth-api/src/utils/request/request.constant.ts @@ -0,0 +1,18 @@ +export enum ENUM_REQUEST_STATUS_CODE_ERROR { + REQUEST_VALIDATION_ERROR = 5981, + REQUEST_TIMESTAMP_INVALID_ERROR = 5982, + REQUEST_USER_AGENT_INVALID_ERROR = 5983, +} + +export enum ENUM_REQUEST_METHOD { + GET = 'GET', + POST = 'POST', + PUT = 'PUT', + PATCH = 'PATCH', + DELETE = 'DELETE', + OPTIONS = 'OPTIONS', + HEAD = 'HEAD', +} + +export const REQUEST_EXCLUDE_TIMESTAMP_META_KEY = + 'RequestExcludeTimestampMetaKey'; diff --git a/packages/auth-api/src/utils/request/request.decorator.ts b/packages/auth-api/src/utils/request/request.decorator.ts new file mode 100644 index 0000000..5819d85 --- /dev/null +++ b/packages/auth-api/src/utils/request/request.decorator.ts @@ -0,0 +1,56 @@ +import { + applyDecorators, + createParamDecorator, + ExecutionContext, + SetMetadata, + UseGuards, +} from '@nestjs/common'; +import { ClassConstructor } from 'class-transformer'; +import { IResult } from 'ua-parser-js'; +import { ParamGuard } from './guard/request.param.guard'; +import { REQUEST_EXCLUDE_TIMESTAMP_META_KEY } from './request.constant'; +import { IRequestApp } from './request.interface'; + +export const RequestUserAgent = createParamDecorator( + (data: string, ctx: ExecutionContext): IResult => { + const { userAgent } = ctx.switchToHttp().getRequest() as IRequestApp; + return userAgent; + } +); + +export const RequestId = createParamDecorator( + (data: string, ctx: ExecutionContext): string => { + const { id } = ctx.switchToHttp().getRequest() as IRequestApp; + return id; + } +); + +export const RequestTimezone = createParamDecorator( + (data: string, ctx: ExecutionContext): string => { + const { timezone } = ctx.switchToHttp().getRequest() as IRequestApp; + return timezone; + } +); + +export const RequestTimestamp = createParamDecorator( + (data: string, ctx: ExecutionContext): string => { + const { timestamp } = ctx.switchToHttp().getRequest() as IRequestApp; + return timestamp; + } +); + +export const RequestCustomLang = createParamDecorator( + (data: string, ctx: ExecutionContext): string => { + const { customLang } = ctx.switchToHttp().getRequest() as IRequestApp; + return customLang; + } +); + +export function RequestParamGuard( + ...classValidation: ClassConstructor[] +): any { + return applyDecorators(UseGuards(ParamGuard(classValidation))); +} + +export const RequestExcludeTimestamp = () => + SetMetadata(REQUEST_EXCLUDE_TIMESTAMP_META_KEY, true); diff --git a/packages/auth-api/src/utils/request/request.interface.ts b/packages/auth-api/src/utils/request/request.interface.ts new file mode 100644 index 0000000..70d3cd0 --- /dev/null +++ b/packages/auth-api/src/utils/request/request.interface.ts @@ -0,0 +1,14 @@ +import { Request } from 'express'; +import { IAuthApiPayload } from 'src/auth/auth.interface'; +import { IResult } from 'ua-parser-js'; + +export interface IRequestApp extends Request { + userAgent?: IResult; + id?: string; + timezone: string; + timestamp: string; + customLang: string; + apiKey?: IAuthApiPayload; + user?: Record; + version?: string; +} diff --git a/packages/auth-api/src/utils/request/request.module.ts b/packages/auth-api/src/utils/request/request.module.ts new file mode 100644 index 0000000..8b30eee --- /dev/null +++ b/packages/auth-api/src/utils/request/request.module.ts @@ -0,0 +1,77 @@ +import { + HttpStatus, + Module, + UnprocessableEntityException, + ValidationError, + ValidationPipe, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { APP_INTERCEPTOR, APP_PIPE, Reflector } from '@nestjs/core'; +import { HelperDateService } from '../helper/service/helper.date.service'; +import { RequestTimestampInterceptor } from './interceptor/request.timestamp.interceptor'; +import { ENUM_REQUEST_STATUS_CODE_ERROR } from './request.constant'; +import { IsPasswordMediumConstraint } from './validation/request.is-password-medium.validation'; +import { IsPasswordStrongConstraint } from './validation/request.is-password-strong.validation'; +import { IsPasswordWeakConstraint } from './validation/request.is-password-weak.validation'; +import { IsStartWithConstraint } from './validation/request.is-start-with.validation'; +import { MaxGreaterThanEqualConstraint } from './validation/request.max-greater-than-equal.validation'; +import { MaxGreaterThanConstraint } from './validation/request.max-greater-than.validation'; +import { MinGreaterThanEqualConstraint } from './validation/request.min-greater-than-equal.validation'; +import { MinGreaterThanConstraint } from './validation/request.min-greater-than.validation'; +import { IsOnlyDigitsConstraint } from './validation/request.only-digits.validation'; +import { SafeStringConstraint } from './validation/request.safe-string.validation'; +import { SkipConstraint } from './validation/request.skip.validation'; +import { StringOrNumberOrBooleanConstraint } from './validation/request.string-or-number-or-boolean.validation'; + +@Module({ + controllers: [], + providers: [ + { + provide: APP_PIPE, + inject: [], + useFactory: () => + new ValidationPipe({ + transform: true, + skipNullProperties: false, + skipUndefinedProperties: false, + skipMissingProperties: false, + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + exceptionFactory: async (errors: ValidationError[]) => + new UnprocessableEntityException({ + statusCode: + ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_VALIDATION_ERROR, + message: 'http.clientError.unprocessableEntity', + errors, + }), + }), + }, + { + provide: APP_INTERCEPTOR, + inject: [ConfigService, Reflector, HelperDateService], + useFactory: ( + configService: ConfigService, + reflector: Reflector, + helperDateService: HelperDateService + ) => + new RequestTimestampInterceptor( + configService, + reflector, + helperDateService + ), + }, + IsPasswordStrongConstraint, + IsPasswordMediumConstraint, + IsPasswordWeakConstraint, + IsStartWithConstraint, + MaxGreaterThanEqualConstraint, + MaxGreaterThanConstraint, + MinGreaterThanEqualConstraint, + MinGreaterThanConstraint, + SkipConstraint, + StringOrNumberOrBooleanConstraint, + SafeStringConstraint, + IsOnlyDigitsConstraint, + ], + imports: [], +}) +export class RequestModule {} diff --git a/packages/auth-api/src/utils/request/validation/request.is-password-medium.validation.ts b/packages/auth-api/src/utils/request/validation/request.is-password-medium.validation.ts new file mode 100644 index 0000000..24b029d --- /dev/null +++ b/packages/auth-api/src/utils/request/validation/request.is-password-medium.validation.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; +import { HelperStringService } from 'src/utils/helper/service/helper.string.service'; + +@ValidatorConstraint({ async: true }) +@Injectable() +export class IsPasswordMediumConstraint + implements ValidatorConstraintInterface +{ + constructor(protected readonly helperStringService: HelperStringService) {} + + validate(value: string, args: ValidationArguments): boolean { + // At least one upper case English letter, (?=.*?[A-Z]) + // At least one lower case English letter, (?=.*?[a-z]) + // At least one digit, (?=.*?[0-9]) + // Minimum eight in length .{8,} (with the anchors) + + const [length] = args.constraints; + return value + ? this.helperStringService.checkPasswordMedium(value, length) + : false; + } +} + +export function IsPasswordMedium( + minLength = 8, + validationOptions?: ValidationOptions +) { + return function (object: Record, propertyName: string): any { + registerDecorator({ + name: 'IsPasswordMedium', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: [minLength], + validator: IsPasswordMediumConstraint, + }); + }; +} diff --git a/packages/auth-api/src/utils/request/validation/request.is-password-strong.validation.ts b/packages/auth-api/src/utils/request/validation/request.is-password-strong.validation.ts new file mode 100644 index 0000000..c0637df --- /dev/null +++ b/packages/auth-api/src/utils/request/validation/request.is-password-strong.validation.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; +import { HelperStringService } from 'src/utils/helper/service/helper.string.service'; + +@ValidatorConstraint({ async: true }) +@Injectable() +export class IsPasswordStrongConstraint + implements ValidatorConstraintInterface +{ + constructor(protected readonly helperStringService: HelperStringService) {} + + validate(value: string, args: ValidationArguments): boolean { + // At least one upper case English letter, (?=.*?[A-Z]) + // At least one lower case English letter, (?=.*?[a-z]) + // At least one digit, (?=.*?[0-9]) + // At least one special character, (?=.*?[#?!@$%^&*-]) + // Minimum eight in length .{8,} (with the anchors) + + const [length] = args.constraints; + return value + ? this.helperStringService.checkPasswordStrong(value, length) + : false; + } +} + +export function IsPasswordStrong( + minLength = 8, + validationOptions?: ValidationOptions +) { + return function (object: Record, propertyName: string): any { + registerDecorator({ + name: 'IsPasswordStrong', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: [minLength], + validator: IsPasswordStrongConstraint, + }); + }; +} diff --git a/packages/auth-api/src/utils/request/validation/request.is-password-weak.validation.ts b/packages/auth-api/src/utils/request/validation/request.is-password-weak.validation.ts new file mode 100644 index 0000000..4d0e3e4 --- /dev/null +++ b/packages/auth-api/src/utils/request/validation/request.is-password-weak.validation.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; +import { HelperStringService } from 'src/utils/helper/service/helper.string.service'; + +@ValidatorConstraint({ async: true }) +@Injectable() +export class IsPasswordWeakConstraint implements ValidatorConstraintInterface { + constructor(protected readonly helperStringService: HelperStringService) {} + + validate(value: string, args: ValidationArguments): boolean { + // At least one upper case English letter, (?=.*?[A-Z]) + // At least one lower case English letter, (?=.*?[a-z]) + // Minimum eight in length .{8,} (with the anchors) + + const [length] = args.constraints; + return value + ? this.helperStringService.checkPasswordMedium(value, length) + : false; + } +} + +export function IsPasswordWeak( + minLength = 8, + validationOptions?: ValidationOptions +) { + return function (object: Record, propertyName: string): any { + registerDecorator({ + name: 'IsPasswordWeak', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: [minLength], + validator: IsPasswordWeakConstraint, + }); + }; +} diff --git a/packages/auth-api/src/utils/request/validation/request.is-start-with.validation.ts b/packages/auth-api/src/utils/request/validation/request.is-start-with.validation.ts new file mode 100644 index 0000000..880be10 --- /dev/null +++ b/packages/auth-api/src/utils/request/validation/request.is-start-with.validation.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; + +@ValidatorConstraint({ async: true }) +@Injectable() +export class IsStartWithConstraint implements ValidatorConstraintInterface { + validate(value: string, args: ValidationArguments): boolean { + const [prefix] = args.constraints; + return value + ? prefix.every((prf: string) => value.startsWith(prf)) + : false; + } +} + +export function IsStartWith( + prefix: string[], + validationOptions?: ValidationOptions +) { + return function (object: Record, propertyName: string): any { + registerDecorator({ + name: 'IsStartWith', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: [prefix], + validator: IsStartWithConstraint, + }); + }; +} diff --git a/packages/auth-api/src/utils/request/validation/request.max-greater-than-equal.validation.ts b/packages/auth-api/src/utils/request/validation/request.max-greater-than-equal.validation.ts new file mode 100644 index 0000000..245a5e9 --- /dev/null +++ b/packages/auth-api/src/utils/request/validation/request.max-greater-than-equal.validation.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; + +@ValidatorConstraint({ async: true }) +@Injectable() +export class MaxGreaterThanEqualConstraint + implements ValidatorConstraintInterface +{ + validate(value: string, args: ValidationArguments): boolean { + const [property] = args.constraints; + const relatedValue = args.object[property]; + return value <= relatedValue; + } +} + +export function MaxGreaterThanEqual( + property: string, + validationOptions?: ValidationOptions +) { + return function (object: Record, propertyName: string): any { + registerDecorator({ + name: 'MaxGreaterThanEqual', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: [property], + validator: MaxGreaterThanEqualConstraint, + }); + }; +} diff --git a/packages/auth-api/src/utils/request/validation/request.max-greater-than.validation.ts b/packages/auth-api/src/utils/request/validation/request.max-greater-than.validation.ts new file mode 100644 index 0000000..1755258 --- /dev/null +++ b/packages/auth-api/src/utils/request/validation/request.max-greater-than.validation.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; + +@ValidatorConstraint({ async: true }) +@Injectable() +export class MaxGreaterThanConstraint implements ValidatorConstraintInterface { + validate(value: string, args: ValidationArguments): boolean { + const [property] = args.constraints; + const relatedValue = args.object[property]; + return value < relatedValue; + } +} + +export function MaxGreaterThan( + property: string, + validationOptions?: ValidationOptions +) { + return function (object: Record, propertyName: string): any { + registerDecorator({ + name: 'MaxGreaterThan', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: [property], + validator: MaxGreaterThanConstraint, + }); + }; +} diff --git a/packages/auth-api/src/utils/request/validation/request.min-greater-than-equal.validation.ts b/packages/auth-api/src/utils/request/validation/request.min-greater-than-equal.validation.ts new file mode 100644 index 0000000..b32d35b --- /dev/null +++ b/packages/auth-api/src/utils/request/validation/request.min-greater-than-equal.validation.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; + +@ValidatorConstraint({ async: true }) +@Injectable() +export class MinGreaterThanEqualConstraint + implements ValidatorConstraintInterface +{ + validate(value: string, args: ValidationArguments): boolean { + const [property] = args.constraints; + const relatedValue = args.object[property]; + return value >= relatedValue; + } +} + +export function MinGreaterThanEqual( + property: string, + validationOptions?: ValidationOptions +) { + return function (object: Record, propertyName: string): any { + registerDecorator({ + name: 'MinGreaterThanEqual', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: [property], + validator: MinGreaterThanEqualConstraint, + }); + }; +} diff --git a/packages/auth-api/src/utils/request/validation/request.min-greater-than.validation.ts b/packages/auth-api/src/utils/request/validation/request.min-greater-than.validation.ts new file mode 100644 index 0000000..5d46c14 --- /dev/null +++ b/packages/auth-api/src/utils/request/validation/request.min-greater-than.validation.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; + +@ValidatorConstraint({ async: true }) +@Injectable() +export class MinGreaterThanConstraint implements ValidatorConstraintInterface { + validate(value: string, args: ValidationArguments): boolean { + const [property] = args.constraints; + const relatedValue = args.object[property]; + return value > relatedValue; + } +} + +export function MinGreaterThan( + property: string, + validationOptions?: ValidationOptions +) { + return function (object: Record, propertyName: string): any { + registerDecorator({ + name: 'MinGreaterThan', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: [property], + validator: MinGreaterThanConstraint, + }); + }; +} diff --git a/packages/auth-api/src/utils/request/validation/request.only-digits.validation.ts b/packages/auth-api/src/utils/request/validation/request.only-digits.validation.ts new file mode 100644 index 0000000..bdd9244 --- /dev/null +++ b/packages/auth-api/src/utils/request/validation/request.only-digits.validation.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; +import { HelperNumberService } from 'src/utils/helper/service/helper.number.service'; + +@ValidatorConstraint({ async: true }) +@Injectable() +export class IsOnlyDigitsConstraint implements ValidatorConstraintInterface { + constructor(protected readonly helperNumberService: HelperNumberService) {} + + validate(value: string): boolean { + return value ? this.helperNumberService.check(value) : false; + } +} + +export function IsOnlyDigits(validationOptions?: ValidationOptions) { + return function (object: Record, propertyName: string): any { + registerDecorator({ + name: 'IsOnlyDigits', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: [], + validator: IsOnlyDigitsConstraint, + }); + }; +} diff --git a/packages/auth-api/src/utils/request/validation/request.safe-string.validation.ts b/packages/auth-api/src/utils/request/validation/request.safe-string.validation.ts new file mode 100644 index 0000000..4f5b08c --- /dev/null +++ b/packages/auth-api/src/utils/request/validation/request.safe-string.validation.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; +import { HelperStringService } from 'src/utils/helper/service/helper.string.service'; + +@ValidatorConstraint({ async: true }) +@Injectable() +export class SafeStringConstraint implements ValidatorConstraintInterface { + constructor(protected readonly helperStringService: HelperStringService) {} + + validate(value: string): boolean { + return value ? this.helperStringService.checkSafeString(value) : false; + } +} + +export function SafeString(validationOptions?: ValidationOptions) { + return function (object: Record, propertyName: string): any { + registerDecorator({ + name: 'SafeString', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + validator: SafeStringConstraint, + }); + }; +} diff --git a/packages/auth-api/src/utils/request/validation/request.skip.validation.ts b/packages/auth-api/src/utils/request/validation/request.skip.validation.ts new file mode 100644 index 0000000..df87f56 --- /dev/null +++ b/packages/auth-api/src/utils/request/validation/request.skip.validation.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; + +@ValidatorConstraint({ async: true }) +@Injectable() +export class SkipConstraint implements ValidatorConstraintInterface { + validate(): boolean { + return true; + } +} + +export function Skip() { + return function (object: Record, propertyName: string): any { + registerDecorator({ + name: 'Skip', + target: object.constructor, + propertyName: propertyName, + validator: SkipConstraint, + }); + }; +} diff --git a/packages/auth-api/src/utils/request/validation/request.string-or-number-or-boolean.validation.ts b/packages/auth-api/src/utils/request/validation/request.string-or-number-or-boolean.validation.ts new file mode 100644 index 0000000..21e1e48 --- /dev/null +++ b/packages/auth-api/src/utils/request/validation/request.string-or-number-or-boolean.validation.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; +import { HelperStringService } from 'src/utils/helper/service/helper.string.service'; + +@ValidatorConstraint({ async: true }) +@Injectable() +export class StringOrNumberOrBooleanConstraint + implements ValidatorConstraintInterface +{ + constructor(protected readonly helperStringService: HelperStringService) {} + + validate(value: string): boolean { + if (typeof value === 'boolean') { + return true; + } + + return value + ? this.helperStringService.checkStringOrNumber(value) + : false; + } +} + +export function StringOrNumberOrBoolean(validationOptions?: ValidationOptions) { + return function (object: Record, propertyName: string): any { + registerDecorator({ + name: 'StringOrNumberOrBoolean', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: [], + validator: StringOrNumberOrBooleanConstraint, + }); + }; +} diff --git a/packages/auth-api/src/utils/response/interceptor/response.custom-headers.interceptor.ts b/packages/auth-api/src/utils/response/interceptor/response.custom-headers.interceptor.ts new file mode 100644 index 0000000..e2b3bad --- /dev/null +++ b/packages/auth-api/src/utils/response/interceptor/response.custom-headers.interceptor.ts @@ -0,0 +1,50 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, +} from '@nestjs/common'; +import { map, Observable } from 'rxjs'; +import { HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { Response } from 'express'; + +@Injectable() +export class ResponseCustomHeadersInterceptor + implements NestInterceptor> +{ + async intercept( + context: ExecutionContext, + next: CallHandler + ): Promise | string>> { + if (context.getType() === 'http') { + return next.handle().pipe( + map(async (response: Promise) => { + const ctx: HttpArgumentsHost = context.switchToHttp(); + const responseExpress: Response = ctx.getResponse(); + const { headers }: Request = ctx.getRequest(); + + responseExpress.setHeader( + 'x-custom-lang', + headers['x-custom-lang'] + ); + responseExpress.setHeader( + 'x-timestamp', + headers['x-timestamp'] + ); + responseExpress.setHeader( + 'x-timezone', + headers['x-timezone'] + ); + responseExpress.setHeader( + 'x-request-id', + headers['x-request-id'] + ); + + return response; + }) + ); + } + + return next.handle(); + } +} diff --git a/packages/auth-api/src/utils/response/interceptor/response.default.interceptor.ts b/packages/auth-api/src/utils/response/interceptor/response.default.interceptor.ts new file mode 100644 index 0000000..aa5e708 --- /dev/null +++ b/packages/auth-api/src/utils/response/interceptor/response.default.interceptor.ts @@ -0,0 +1,67 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, + mixin, + Type, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { IMessage } from 'src/message/message.interface'; +import { MessageService } from 'src/message/service/message.service'; +import { IResponseOptions } from '../response.interface'; +import { Response } from 'express'; +import { IRequestApp } from 'src/utils/request/request.interface'; + +export function ResponseDefaultInterceptor( + messagePath: string, + options?: IResponseOptions +): Type { + @Injectable() + class MixinResponseDefaultInterceptor + implements NestInterceptor> + { + constructor(private readonly messageService: MessageService) {} + + async intercept( + context: ExecutionContext, + next: CallHandler + ): Promise | string>> { + if (context.getType() === 'http') { + return next.handle().pipe( + map(async (response: Promise>) => { + const ctx: HttpArgumentsHost = context.switchToHttp(); + const responseExpress: Response = ctx.getResponse(); + const { customLang } = ctx.getRequest(); + const customLanguages = customLang.split(','); + + const statusCode: number = + options && options.statusCode + ? options.statusCode + : responseExpress.statusCode; + const data: Record = await response; + const message: string | IMessage = + (await this.messageService.get(messagePath, { + customLanguages, + })) || + (await this.messageService.get('response.default', { + customLanguages, + })); + + return { + statusCode, + message, + data, + }; + }) + ); + } + + return next.handle(); + } + } + + return mixin(MixinResponseDefaultInterceptor); +} diff --git a/packages/auth-api/src/utils/response/interceptor/response.paging.interceptor.ts b/packages/auth-api/src/utils/response/interceptor/response.paging.interceptor.ts new file mode 100644 index 0000000..24f1751 --- /dev/null +++ b/packages/auth-api/src/utils/response/interceptor/response.paging.interceptor.ts @@ -0,0 +1,120 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, + mixin, + Type, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { IMessage } from 'src/message/message.interface'; +import { MessageService } from 'src/message/service/message.service'; +import { + ENUM_PAGINATION_TYPE, + PAGINATION_DEFAULT_MAX_PAGE, +} from 'src/pagination/pagination.constant'; +import { IResponsePagingOptions } from '../response.interface'; +import { Response } from 'express'; + +// This interceptor for restructure response success +export function ResponsePagingInterceptor( + messagePath: string, + options?: IResponsePagingOptions +): Type { + @Injectable() + class MixinResponseInterceptor implements NestInterceptor> { + constructor(private readonly messageService: MessageService) {} + + async intercept( + context: ExecutionContext, + next: CallHandler + ): Promise | string>> { + if (context.getType() === 'http') { + return next.handle().pipe( + map(async (response: Promise>) => { + const ctx: HttpArgumentsHost = context.switchToHttp(); + const responseExpress: Response = ctx.getResponse(); + const { headers } = ctx.getRequest(); + const customLanguages = headers['x-custom-lang']; + + const statusCode: number = + options && options.statusCode + ? options.statusCode + : responseExpress.statusCode; + const responseData: Record = + await response; + const { + totalData, + currentPage, + perPage, + data, + metadata, + availableSort, + availableSearch, + } = responseData; + + let { totalPage } = responseData; + totalPage = + totalPage > PAGINATION_DEFAULT_MAX_PAGE + ? PAGINATION_DEFAULT_MAX_PAGE + : totalPage; + + const message: string | IMessage = + (await this.messageService.get(messagePath, { + customLanguages, + })) || + (await this.messageService.get('response.default', { + customLanguages, + })); + + if ( + options && + options.type === ENUM_PAGINATION_TYPE.SIMPLE + ) { + return { + statusCode, + message, + totalData, + totalPage, + currentPage, + perPage, + metadata, + data, + }; + } else if ( + options && + options.type === ENUM_PAGINATION_TYPE.MINI + ) { + return { + statusCode, + message, + totalData, + metadata, + data, + }; + } + + return { + statusCode, + message, + totalData, + totalPage, + currentPage, + perPage, + availableSort, + availableSearch, + metadata, + data, + }; + }) + ); + } + + return next.handle(); + } + } + + return mixin(MixinResponseInterceptor); +} diff --git a/packages/auth-api/src/utils/response/interceptor/response.timeout.interceptor.ts b/packages/auth-api/src/utils/response/interceptor/response.timeout.interceptor.ts new file mode 100644 index 0000000..0eaf7c1 --- /dev/null +++ b/packages/auth-api/src/utils/response/interceptor/response.timeout.interceptor.ts @@ -0,0 +1,92 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, + RequestTimeoutException, + Type, + mixin, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { Reflector } from '@nestjs/core'; +import { Observable, throwError, TimeoutError } from 'rxjs'; +import { catchError, timeout } from 'rxjs/operators'; +import { ENUM_STATUS_CODE_ERROR } from 'src/utils/error/error.constant'; +import ms from 'ms'; +import { RESPONSE_CUSTOM_TIMEOUT_META_KEY } from '../response.constant'; + +export function ResponseTimeoutInterceptor( + seconds: string +): Type { + @Injectable() + class MixinResponseTimeoutInterceptor + implements NestInterceptor> + { + async intercept( + context: ExecutionContext, + next: CallHandler + ): Promise | string>> { + if (context.getType() === 'http') { + return next.handle().pipe( + timeout(ms(seconds)), + catchError((err) => { + if (err instanceof TimeoutError) { + throw new RequestTimeoutException({ + statusCode: + ENUM_STATUS_CODE_ERROR.REQUEST_TIMEOUT, + message: 'http.clientError.requestTimeOut', + }); + } + return throwError(() => err); + }) + ); + } + + return next.handle(); + } + } + + return mixin(MixinResponseTimeoutInterceptor); +} + +export class ResponseTimeoutDefaultInterceptor + implements NestInterceptor> +{ + constructor( + private readonly configService: ConfigService, + private readonly reflector: Reflector + ) {} + + async intercept( + context: ExecutionContext, + next: CallHandler + ): Promise | string>> { + if (context.getType() === 'http') { + const customTimeout = this.reflector.get( + RESPONSE_CUSTOM_TIMEOUT_META_KEY, + context.getHandler() + ); + + if (!customTimeout) { + const defaultTimeout: number = this.configService.get( + 'middleware.timeout.in' + ); + return next.handle().pipe( + timeout(defaultTimeout), + catchError((err) => { + if (err instanceof TimeoutError) { + throw new RequestTimeoutException({ + statusCode: + ENUM_STATUS_CODE_ERROR.REQUEST_TIMEOUT, + message: 'http.clientError.requestTimeOut', + }); + } + return throwError(() => err); + }) + ); + } + } + + return next.handle(); + } +} diff --git a/packages/auth-api/src/utils/response/response.constant.ts b/packages/auth-api/src/utils/response/response.constant.ts new file mode 100644 index 0000000..9abcb5d --- /dev/null +++ b/packages/auth-api/src/utils/response/response.constant.ts @@ -0,0 +1 @@ +export const RESPONSE_CUSTOM_TIMEOUT_META_KEY = 'ResponseCustomTimeoutMetaKey'; diff --git a/packages/auth-api/src/utils/response/response.decorator.ts b/packages/auth-api/src/utils/response/response.decorator.ts new file mode 100644 index 0000000..ce2cc47 --- /dev/null +++ b/packages/auth-api/src/utils/response/response.decorator.ts @@ -0,0 +1,28 @@ +import { applyDecorators, SetMetadata, UseInterceptors } from '@nestjs/common'; +import { ResponseDefaultInterceptor } from './interceptor/response.default.interceptor'; +import { ResponsePagingInterceptor } from './interceptor/response.paging.interceptor'; +import { ResponseTimeoutInterceptor } from './interceptor/response.timeout.interceptor'; +import { RESPONSE_CUSTOM_TIMEOUT_META_KEY } from './response.constant'; +import { IResponseOptions, IResponsePagingOptions } from './response.interface'; + +export function Response(messagePath: string, options?: IResponseOptions): any { + return applyDecorators( + UseInterceptors(ResponseDefaultInterceptor(messagePath, options)) + ); +} + +export function ResponsePaging( + messagePath: string, + options?: IResponsePagingOptions +): any { + return applyDecorators( + UseInterceptors(ResponsePagingInterceptor(messagePath, options)) + ); +} + +export function ResponseTimeout(seconds: string): any { + return applyDecorators( + SetMetadata(RESPONSE_CUSTOM_TIMEOUT_META_KEY, true), + UseInterceptors(ResponseTimeoutInterceptor(seconds)) + ); +} diff --git a/packages/auth-api/src/utils/response/response.interface.ts b/packages/auth-api/src/utils/response/response.interface.ts new file mode 100644 index 0000000..6b9aa0f --- /dev/null +++ b/packages/auth-api/src/utils/response/response.interface.ts @@ -0,0 +1,25 @@ +import { ENUM_PAGINATION_TYPE } from 'src/pagination/pagination.constant'; + +export type IResponse = Record; + +export interface IResponsePaging { + totalData: number; + totalPage?: number; + currentPage?: number; + perPage?: number; + availableSearch?: string[]; + availableSort?: string[]; + metadata?: Record; + data: Record[]; +} + +export interface IResponseOptions { + statusCode?: number; + timeout?: number; +} + +export interface IResponsePagingOptions { + statusCode?: number; + type?: ENUM_PAGINATION_TYPE; + timeout?: number; +} diff --git a/packages/auth-api/src/utils/response/response.module.ts b/packages/auth-api/src/utils/response/response.module.ts new file mode 100644 index 0000000..5e069c0 --- /dev/null +++ b/packages/auth-api/src/utils/response/response.module.ts @@ -0,0 +1,23 @@ +import { Module } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { APP_INTERCEPTOR, Reflector } from '@nestjs/core'; +import { ResponseCustomHeadersInterceptor } from './interceptor/response.custom-headers.interceptor'; +import { ResponseTimeoutDefaultInterceptor } from './interceptor/response.timeout.interceptor'; + +@Module({ + controllers: [], + providers: [ + { + provide: APP_INTERCEPTOR, + inject: [ConfigService, Reflector], + useFactory: (configService: ConfigService, reflector: Reflector) => + new ResponseTimeoutDefaultInterceptor(configService, reflector), + }, + { + provide: APP_INTERCEPTOR, + useFactory: () => new ResponseCustomHeadersInterceptor(), + }, + ], + imports: [], +}) +export class ResponseModule {} diff --git a/packages/auth-api/src/utils/version/interceptor/version.interceptor.ts b/packages/auth-api/src/utils/version/interceptor/version.interceptor.ts new file mode 100644 index 0000000..04d36dc --- /dev/null +++ b/packages/auth-api/src/utils/version/interceptor/version.interceptor.ts @@ -0,0 +1,46 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { ConfigService } from '@nestjs/config'; +import { IRequestApp } from 'src/utils/request/request.interface'; + +@Injectable() +export class VersionInterceptor implements NestInterceptor> { + constructor(private readonly configService: ConfigService) {} + + async intercept( + context: ExecutionContext, + next: CallHandler + ): Promise | string>> { + if (context.getType() === 'http') { + const ctx: HttpArgumentsHost = context.switchToHttp(); + + const globalPrefix: boolean = + this.configService.get('app.globalPrefix'); + const versioning: boolean = + this.configService.get('app.versioning.on'); + const versioningPrefix: string = this.configService.get( + 'app.versioning.prefix' + ); + const request = ctx.getRequest(); + const originalUrl: string = request.url; + + if ( + versioning && + originalUrl.startsWith(`${globalPrefix}/${versioningPrefix}`) + ) { + const url: string[] = originalUrl.split('/'); + const version: string = url[2].replace(versioningPrefix, ''); + + request.version = version; + } + } + + return next.handle(); + } +} diff --git a/packages/auth-api/src/utils/version/version.decorator.ts b/packages/auth-api/src/utils/version/version.decorator.ts new file mode 100644 index 0000000..33d451a --- /dev/null +++ b/packages/auth-api/src/utils/version/version.decorator.ts @@ -0,0 +1,8 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +export const GetVersion = createParamDecorator( + (data: string, ctx: ExecutionContext) => { + const { __version } = ctx.switchToHttp().getRequest(); + return __version; + } +); diff --git a/packages/auth-api/src/utils/version/version.interface.ts b/packages/auth-api/src/utils/version/version.interface.ts new file mode 100644 index 0000000..a17b0a2 --- /dev/null +++ b/packages/auth-api/src/utils/version/version.interface.ts @@ -0,0 +1,3 @@ +export interface IVersionSerializationOptions { + version: number; +} diff --git a/packages/auth-api/src/utils/version/version.module.ts b/packages/auth-api/src/utils/version/version.module.ts new file mode 100644 index 0000000..fa6096b --- /dev/null +++ b/packages/auth-api/src/utils/version/version.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { APP_INTERCEPTOR } from '@nestjs/core'; +import { VersionInterceptor } from './interceptor/version.interceptor'; + +@Module({ + controllers: [], + providers: [ + { + provide: APP_INTERCEPTOR, + inject: [ConfigService], + useFactory: (configService: ConfigService) => + new VersionInterceptor(configService), + }, + ], + imports: [], +}) +export class VersionModule {} diff --git a/packages/auth-api/test/auth/auth.service.spec.ts b/packages/auth-api/test/auth/auth.service.spec.ts new file mode 100644 index 0000000..5eb777b --- /dev/null +++ b/packages/auth-api/test/auth/auth.service.spec.ts @@ -0,0 +1,470 @@ +import { Test } from '@nestjs/testing'; +import { AuthService } from 'src/auth/service/auth.service'; +import { CoreModule } from 'src/core/core.module'; +import { IRoleDocument } from 'src/role/role.interface'; +import { IUserDocument } from 'src/user/user.interface'; +import { faker } from '@faker-js/faker'; + +describe('AuthService', () => { + let authService: AuthService; + + const rememberMe = false; + + // cSpell:ignore ZfqgaDMPpWQ3lJEGQ8Ueu stnk + const user: IUserDocument = { + _id: '623cb7fd37a861a10bac2c91', + isActive: true, + salt: '$2b$08$GZfqgaDMPpWQ3lJEGQ8Ueu', + passwordExpired: new Date('2023-03-24T18:27:09.500Z'), + password: + '$2b$08$GZfqgaDMPpWQ3lJEGQ8Ueu1vJ3C6G3stnkS/5e61bK/4f1.Fuw2Eq', + role: { + _id: '623cb7f7965a74bf7a0e9e53', + isAdmin: true, + isActive: true, + permissions: [], + name: 'admin', + } as IRoleDocument, + email: 'admin@mail.com', + mobileNumber: '08111111111', + lastName: 'test', + firstName: 'admin@mail.com', + } as IUserDocument; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + providers: [AuthService], + }).compile(); + + authService = moduleRef.get(AuthService); + }); + + it('should be defined', async () => { + expect(authService).toBeDefined(); + }); + + describe('serializationLogin', () => { + it('should be called', async () => { + const test = jest.spyOn(authService, 'serializationLogin'); + + await authService.serializationLogin(user); + expect(test).toHaveBeenCalledWith(user); + }); + + it('should be mapped', async () => { + const map = await authService.serializationLogin(user); + jest.spyOn(authService, 'serializationLogin').mockImplementation( + async () => map + ); + + expect(await authService.serializationLogin(user)).toBe(map); + }); + }); + + describe('createPayloadAccessToken', () => { + it('should be called', async () => { + const test = jest.spyOn(authService, 'createPayloadAccessToken'); + + const map = await authService.serializationLogin(user); + await authService.createPayloadAccessToken(map, rememberMe); + expect(test).toHaveBeenCalledWith(map, rememberMe); + }); + + it('should be mapped', async () => { + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadAccessToken( + map, + rememberMe + ); + jest.spyOn( + authService, + 'createPayloadAccessToken' + ).mockImplementation(async () => payload); + + expect( + await authService.createPayloadAccessToken(map, rememberMe) + ).toBe(payload); + }); + + it('login date should be mapped', async () => { + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadAccessToken( + map, + rememberMe, + { loginDate: new Date() } + ); + jest.spyOn( + authService, + 'createPayloadAccessToken' + ).mockImplementation(async () => payload); + + expect( + await authService.createPayloadAccessToken(map, rememberMe, { + loginDate: new Date(), + }) + ).toBe(payload); + }); + }); + + describe('createAccessToken', () => { + it('should be called', async () => { + const test = jest.spyOn(authService, 'createAccessToken'); + + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadAccessToken( + map, + rememberMe + ); + await authService.createAccessToken(payload); + expect(test).toHaveBeenCalledWith(payload); + }); + + it('should be mapped', async () => { + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadAccessToken( + map, + rememberMe + ); + const accessToken = await authService.createAccessToken(payload); + jest.spyOn(authService, 'createAccessToken').mockImplementation( + async () => accessToken + ); + + expect(await authService.createAccessToken(payload)).toBe( + accessToken + ); + }); + }); + + describe('validateAccessToken', () => { + it('should be called', async () => { + const test = jest.spyOn(authService, 'validateAccessToken'); + + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadAccessToken( + map, + rememberMe + ); + const accessToken = await authService.createAccessToken(payload); + await authService.validateAccessToken(accessToken); + expect(test).toHaveBeenCalledWith(accessToken); + }); + + it('should be success', async () => { + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadAccessToken( + map, + rememberMe + ); + const accessToken = await authService.createAccessToken(payload); + const validate = await authService.validateAccessToken(accessToken); + jest.spyOn(authService, 'validateAccessToken').mockImplementation( + async () => validate + ); + + expect(await authService.validateAccessToken(accessToken)).toBe( + validate + ); + }); + }); + + describe('payloadAccessToken', () => { + it('should be called', async () => { + const test = jest.spyOn(authService, 'payloadAccessToken'); + + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadAccessToken( + map, + rememberMe + ); + const accessToken = await authService.createAccessToken(payload); + await authService.payloadAccessToken(accessToken); + expect(test).toHaveBeenCalledWith(accessToken); + }); + + it('should be success', async () => { + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadAccessToken( + map, + rememberMe + ); + const accessToken = await authService.createAccessToken(payload); + jest.spyOn(authService, 'payloadAccessToken').mockImplementation( + async () => payload + ); + + expect(await authService.payloadAccessToken(accessToken)).toBe( + payload + ); + }); + }); + + describe('createPayloadRefreshToken', () => { + it('should be called', async () => { + const test = jest.spyOn(authService, 'createPayloadRefreshToken'); + + const map = await authService.serializationLogin(user); + await authService.createPayloadRefreshToken(map, rememberMe); + expect(test).toHaveBeenCalledWith(map, rememberMe); + }); + + it('should be success', async () => { + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadRefreshToken( + map, + rememberMe + ); + jest.spyOn( + authService, + 'createPayloadRefreshToken' + ).mockImplementation(async () => payload); + + expect( + await authService.createPayloadRefreshToken(map, rememberMe) + ).toBe(payload); + }); + + it('login date should be success', async () => { + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadRefreshToken( + map, + rememberMe, + { loginDate: new Date() } + ); + jest.spyOn( + authService, + 'createPayloadRefreshToken' + ).mockImplementation(async () => payload); + + expect( + await authService.createPayloadRefreshToken(map, rememberMe, { + loginDate: new Date(), + }) + ).toBe(payload); + }); + }); + + describe('createRefreshToken', () => { + it('should be called', async () => { + const test = jest.spyOn(authService, 'createRefreshToken'); + + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadRefreshToken( + map, + rememberMe + ); + await authService.createRefreshToken(payload, rememberMe); + expect(test).toHaveBeenCalledWith(payload, rememberMe); + }); + + it('should be success', async () => { + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadRefreshToken( + map, + rememberMe + ); + const refreshToken = await authService.createRefreshToken( + payload, + rememberMe + ); + jest.spyOn(authService, 'createRefreshToken').mockImplementation( + async () => refreshToken + ); + + expect( + await authService.createRefreshToken(payload, rememberMe) + ).toBe(refreshToken); + }); + + it('remember me should be success', async () => { + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadRefreshToken( + map, + true + ); + const refreshToken = await authService.createRefreshToken( + payload, + true + ); + jest.spyOn(authService, 'createRefreshToken').mockImplementation( + async () => refreshToken + ); + + expect(await authService.createRefreshToken(payload, true)).toBe( + refreshToken + ); + }); + }); + + describe('validateRefreshToken', () => { + it('should be called', async () => { + const test = jest.spyOn(authService, 'validateRefreshToken'); + + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadRefreshToken( + map, + rememberMe + ); + const refreshToken = await authService.createRefreshToken( + payload, + rememberMe, + true + ); + await authService.validateRefreshToken(refreshToken); + expect(test).toHaveBeenCalledWith(refreshToken); + }); + + it('should be success', async () => { + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadRefreshToken( + map, + rememberMe + ); + const refreshToken = await authService.createRefreshToken( + payload, + rememberMe, + true + ); + const validate = await authService.validateRefreshToken( + refreshToken + ); + jest.spyOn(authService, 'validateRefreshToken').mockImplementation( + async () => validate + ); + + expect(await authService.validateRefreshToken(refreshToken)).toBe( + validate + ); + }); + }); + + describe('payloadRefreshToken', () => { + it('should be called', async () => { + const test = jest.spyOn(authService, 'payloadRefreshToken'); + + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadRefreshToken( + map, + rememberMe + ); + const refreshToken = await authService.createRefreshToken( + payload, + rememberMe, + true + ); + await authService.payloadRefreshToken(refreshToken); + expect(test).toHaveBeenCalledWith(refreshToken); + }); + + it('should be success', async () => { + const map = await authService.serializationLogin(user); + const payload = await authService.createPayloadRefreshToken( + map, + rememberMe + ); + const refreshToken = await authService.createRefreshToken( + payload, + rememberMe + ); + jest.spyOn(authService, 'payloadRefreshToken').mockImplementation( + async () => payload + ); + + expect(await authService.payloadRefreshToken(refreshToken)).toBe( + payload + ); + }); + }); + + describe('createPassword', () => { + it('should be called', async () => { + const test = jest.spyOn(authService, 'createPassword'); + const password = faker.internet.password(20, true, /[A-Za-z0-9]/); + + await authService.createPassword(password); + expect(test).toHaveBeenCalledWith(password); + }); + + it('should be success', async () => { + const password = faker.internet.password(20, true, /[A-Za-z0-9]/); + const passwordHash = await authService.createPassword(password); + + jest.spyOn(authService, 'createPassword').mockImplementation( + async () => passwordHash + ); + + expect(await authService.createPassword(password)).toBe( + passwordHash + ); + }); + }); + + describe('validateUser', () => { + it('should be called', async () => { + const test = jest.spyOn(authService, 'validateUser'); + const password = faker.internet.password(20, true, /[A-Za-z0-9]/); + const passwordHash = await authService.createPassword(password); + + await authService.validateUser(password, passwordHash.passwordHash); + expect(test).toHaveBeenCalledWith( + password, + passwordHash.passwordHash + ); + }); + + it('should be success', async () => { + const password = faker.internet.password(20, true, /[A-Za-z0-9]/); + const passwordHash = await authService.createPassword(password); + const validate = await authService.validateUser( + password, + passwordHash.passwordHash + ); + + jest.spyOn(authService, 'validateUser').mockImplementation( + async () => validate + ); + + expect( + await authService.validateUser( + password, + passwordHash.passwordHash + ) + ).toBe(validate); + }); + }); + + describe('checkPasswordExpired', () => { + it('should be called', async () => { + const test = jest.spyOn(authService, 'checkPasswordExpired'); + + await authService.checkPasswordExpired(user.passwordExpired); + expect(test).toHaveBeenCalledWith(user.passwordExpired); + }); + + it('should be success false', async () => { + const result = await authService.checkPasswordExpired( + user.passwordExpired + ); + jest.spyOn(authService, 'checkPasswordExpired').mockImplementation( + async () => result + ); + + expect( + await authService.checkPasswordExpired(user.passwordExpired) + ).toBe(result); + }); + + it('should be success true', async () => { + const expiredDate = new Date('1999-01-01'); + const result = await authService.checkPasswordExpired(expiredDate); + jest.spyOn(authService, 'checkPasswordExpired').mockImplementation( + async () => result + ); + + expect(await authService.checkPasswordExpired(expiredDate)).toBe( + result + ); + }); + }); +}); diff --git a/packages/auth-api/test/cache/cache.options.spec.ts b/packages/auth-api/test/cache/cache.options.spec.ts new file mode 100644 index 0000000..2bff9c2 --- /dev/null +++ b/packages/auth-api/test/cache/cache.options.spec.ts @@ -0,0 +1,39 @@ +import { Test } from '@nestjs/testing'; +import { CacheOptionsService } from 'src/cache/service/cache.options.service'; +import { CoreModule } from 'src/core/core.module'; + +describe('CacheOptionsService', () => { + let cacheOptionsService: CacheOptionsService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule, CacheOptionsService], + }).compile(); + + cacheOptionsService = + moduleRef.get(CacheOptionsService); + }); + + it('should be defined', () => { + expect(cacheOptionsService).toBeDefined(); + }); + + describe('createCacheOptions', () => { + it('should be called', async () => { + const test = jest.spyOn(cacheOptionsService, 'createCacheOptions'); + + cacheOptionsService.createCacheOptions(); + expect(test).toHaveBeenCalled(); + }); + + it('should be success', async () => { + const options = cacheOptionsService.createCacheOptions(); + jest.spyOn( + cacheOptionsService, + 'createCacheOptions' + ).mockImplementation(() => options); + + expect(cacheOptionsService.createCacheOptions()).toBe(options); + }); + }); +}); diff --git a/packages/auth-api/test/cache/cache.service.spec.ts b/packages/auth-api/test/cache/cache.service.spec.ts new file mode 100644 index 0000000..06e732b --- /dev/null +++ b/packages/auth-api/test/cache/cache.service.spec.ts @@ -0,0 +1,103 @@ +import { Test } from '@nestjs/testing'; +import { CacheService } from 'src/cache/service/cache.service'; +import { CoreModule } from 'src/core/core.module'; + +describe('CacheService', () => { + let cacheService: CacheService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + cacheService = moduleRef.get(CacheService); + }); + + it('should be defined', () => { + expect(cacheService).toBeDefined(); + }); + + describe('get', () => { + it('should be called', async () => { + const test = jest.spyOn(cacheService, 'get'); + + cacheService.get('app.env'); + expect(test).toHaveBeenCalledWith('app.env'); + }); + + it('should be success', async () => { + const env = cacheService.get('app.env'); + jest.spyOn(cacheService, 'get').mockImplementation(() => env); + + expect(cacheService.get('app.env')).toBe(env); + }); + }); + + describe('set', () => { + it('should be called', async () => { + const test = jest.spyOn(cacheService, 'set'); + + cacheService.set('app.env', 1234567890); + expect(test).toHaveBeenCalledWith('app.env', 1234567890); + }); + + it('should be success', async () => { + const env = cacheService.set('app.env', 1234567890); + jest.spyOn(cacheService, 'set').mockImplementation(() => env); + + expect(cacheService.set('app.env', 1234567890)).toBe(env); + }); + }); + + describe('setNoLimit', () => { + it('should be called', async () => { + const test = jest.spyOn(cacheService, 'setNoLimit'); + + cacheService.setNoLimit('app.env.test', 1234567890); + expect(test).toHaveBeenCalledWith('app.env.test', 1234567890); + }); + + it('should be success', async () => { + const env = cacheService.setNoLimit('app.env.test', 1234567890); + jest.spyOn(cacheService, 'setNoLimit').mockImplementation( + () => env + ); + + expect(cacheService.setNoLimit('app.env.test', 1234567890)).toBe( + env + ); + }); + }); + + describe('delete', () => { + it('should be called', async () => { + const test = jest.spyOn(cacheService, 'delete'); + + cacheService.delete('app.env.test'); + expect(test).toHaveBeenCalledWith('app.env.test'); + }); + + it('should be success', async () => { + const env = cacheService.delete('app.env.test'); + jest.spyOn(cacheService, 'delete').mockImplementation(() => env); + + expect(cacheService.delete('app.env.test')).toBe(env); + }); + }); + + describe('reset', () => { + it('should be called', async () => { + const test = jest.spyOn(cacheService, 'reset'); + + cacheService.reset(); + expect(test).toHaveBeenCalledWith(); + }); + + it('should be called', async () => { + const env = cacheService.reset(); + jest.spyOn(cacheService, 'reset').mockImplementation(() => env); + + expect(cacheService.reset()).toBe(env); + }); + }); +}); diff --git a/packages/auth-api/test/config/config.service.spec.ts b/packages/auth-api/test/config/config.service.spec.ts new file mode 100644 index 0000000..4d37249 --- /dev/null +++ b/packages/auth-api/test/config/config.service.spec.ts @@ -0,0 +1,35 @@ +import { ConfigService } from '@nestjs/config'; +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; + +describe('ConfigService', () => { + let configService: ConfigService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + configService = moduleRef.get(ConfigService); + }); + + it('should be defined', () => { + expect(configService).toBeDefined(); + }); + + describe('get', () => { + it('should be called', async () => { + const test = jest.spyOn(configService, 'get'); + + configService.get('auth.env'); + expect(test).toHaveBeenCalledWith('auth.env'); + }); + + it('should be success', async () => { + const env = configService.get('app.env'); + jest.spyOn(configService, 'get').mockImplementation(() => env); + + expect(configService.get('app.env')).toBe(env); + }); + }); +}); diff --git a/packages/auth-api/test/database/database.service.spec.ts b/packages/auth-api/test/database/database.service.spec.ts new file mode 100644 index 0000000..ca2884f --- /dev/null +++ b/packages/auth-api/test/database/database.service.spec.ts @@ -0,0 +1,45 @@ +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { DatabaseOptionsService } from 'src/database/service/database.options.service'; + +describe('DatabaseOptionsService', () => { + let databaseOptionsService: DatabaseOptionsService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + databaseOptionsService = moduleRef.get( + DatabaseOptionsService + ); + }); + + it('should be defined', () => { + expect(databaseOptionsService).toBeDefined(); + }); + + describe('createMongooseOptions', () => { + it('should be called', async () => { + const test = jest.spyOn( + databaseOptionsService, + 'createMongooseOptions' + ); + + databaseOptionsService.createMongooseOptions(); + expect(test).toHaveBeenCalled(); + }); + + it('should be success', async () => { + const options = databaseOptionsService.createMongooseOptions(); + jest.spyOn( + databaseOptionsService, + 'createMongooseOptions' + ).mockImplementation(() => options); + + expect(databaseOptionsService.createMongooseOptions()).toBe( + options + ); + }); + }); +}); diff --git a/packages/auth-api/test/debugger/debugger.options.service.spec.ts b/packages/auth-api/test/debugger/debugger.options.service.spec.ts new file mode 100644 index 0000000..e712602 --- /dev/null +++ b/packages/auth-api/test/debugger/debugger.options.service.spec.ts @@ -0,0 +1,40 @@ +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { DebuggerOptionService } from 'src/debugger/service/debugger.option.service'; + +describe('DebuggerOptionService', () => { + let debuggerOptionService: DebuggerOptionService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + debuggerOptionService = moduleRef.get( + DebuggerOptionService + ); + }); + + it('should be defined', () => { + expect(debuggerOptionService).toBeDefined(); + }); + + describe('info', () => { + it('should be called', async () => { + const test = jest.spyOn(debuggerOptionService, 'createLogger'); + + debuggerOptionService.createLogger(); + expect(test).toHaveBeenCalled(); + }); + + it('should be success', async () => { + const options = debuggerOptionService.createLogger(); + jest.spyOn( + debuggerOptionService, + 'createLogger' + ).mockImplementation(() => options); + + expect(debuggerOptionService.createLogger()).toBe(options); + }); + }); +}); diff --git a/packages/auth-api/test/debugger/debugger.service.spec.ts b/packages/auth-api/test/debugger/debugger.service.spec.ts new file mode 100644 index 0000000..e6207a6 --- /dev/null +++ b/packages/auth-api/test/debugger/debugger.service.spec.ts @@ -0,0 +1,144 @@ +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { DebuggerService } from 'src/debugger/service/debugger.service'; + +describe('DebuggerService', () => { + let debuggerService: DebuggerService; + + const sDescription = 'test description'; + const sClass = 'test class'; + const cFunction = 'test function'; + const data = { test: 'test' }; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + debuggerService = moduleRef.get(DebuggerService); + }); + + it('should be defined', () => { + expect(debuggerService).toBeDefined(); + }); + + describe('info', () => { + it('should be called', async () => { + const test = jest.spyOn(debuggerService, 'info'); + + debuggerService.info('DebuggerService', { + description: sDescription, + class: sClass, + function: cFunction, + }); + expect(test).toHaveBeenCalledWith('DebuggerService', { + description: sDescription, + class: sClass, + function: cFunction, + }); + }); + + it('should be called with data', async () => { + const test = jest.spyOn(debuggerService, 'info'); + + debuggerService.info( + 'DebuggerService', + { + description: sDescription, + class: sClass, + function: cFunction, + }, + data + ); + expect(test).toHaveBeenCalledWith( + 'DebuggerService', + { + description: sDescription, + class: sClass, + function: cFunction, + }, + data + ); + }); + }); + + describe('debug', () => { + it('should be called', async () => { + const test = jest.spyOn(debuggerService, 'debug'); + + debuggerService.debug('DebuggerService', { + description: sDescription, + class: sClass, + function: cFunction, + }); + expect(test).toHaveBeenCalledWith('DebuggerService', { + description: sDescription, + class: sClass, + function: cFunction, + }); + }); + + it('should be called with data', async () => { + const test = jest.spyOn(debuggerService, 'debug'); + + debuggerService.debug( + 'DebuggerService', + { + description: sDescription, + class: sClass, + function: cFunction, + }, + data + ); + expect(test).toHaveBeenCalledWith( + 'DebuggerService', + { + description: sDescription, + class: sClass, + function: cFunction, + }, + data + ); + }); + }); + + describe('error', () => { + it('should be called', async () => { + const test = jest.spyOn(debuggerService, 'error'); + + debuggerService.error('DebuggerService', { + description: sDescription, + class: sClass, + function: cFunction, + }); + expect(test).toHaveBeenCalledWith('DebuggerService', { + description: sDescription, + class: sClass, + function: cFunction, + }); + }); + + it('should be called with data', async () => { + const test = jest.spyOn(debuggerService, 'error'); + + debuggerService.error( + 'DebuggerService', + { + description: sDescription, + class: sClass, + function: cFunction, + }, + data + ); + expect(test).toHaveBeenCalledWith( + 'DebuggerService', + { + description: sDescription, + class: sClass, + function: cFunction, + }, + data + ); + }); + }); +}); diff --git a/packages/auth-api/test/helper/helper.array.service.spec.ts b/packages/auth-api/test/helper/helper.array.service.spec.ts new file mode 100644 index 0000000..309790b --- /dev/null +++ b/packages/auth-api/test/helper/helper.array.service.spec.ts @@ -0,0 +1,510 @@ +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { HelperArrayService } from 'src/utils/helper/service/helper.array.service'; + +describe('HelperArrayService', () => { + let helperArrayService: HelperArrayService; + const arrays = [1, '2', '3', 3, 1, 6, 7, 8]; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + helperArrayService = + moduleRef.get(HelperArrayService); + }); + + it('should be defined', () => { + expect(helperArrayService).toBeDefined(); + }); + + describe('getLeftByIndex', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'getLeftByIndex'); + + helperArrayService.getLeftByIndex(arrays, 1); + expect(test).toHaveBeenCalledWith(arrays, 1); + }); + + it('should be success', async () => { + const result = helperArrayService.getLeftByIndex(arrays, 1); + jest.spyOn(helperArrayService, 'getLeftByIndex').mockImplementation( + () => result + ); + + expect(helperArrayService.getLeftByIndex(arrays, 1)).toBe(result); + }); + }); + + describe('getRightByIndex', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'getRightByIndex'); + + helperArrayService.getRightByIndex(arrays, 1); + expect(test).toHaveBeenCalledWith(arrays, 1); + }); + + it('should be success', async () => { + const result = helperArrayService.getRightByIndex(arrays, 1); + jest.spyOn( + helperArrayService, + 'getRightByIndex' + ).mockImplementation(() => result); + + expect(helperArrayService.getRightByIndex(arrays, 1)).toBe(result); + }); + }); + + describe('getLeftByLength', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'getLeftByLength'); + + helperArrayService.getLeftByLength(arrays, 1); + expect(test).toHaveBeenCalledWith(arrays, 1); + }); + + it('should be success', async () => { + const result = helperArrayService.getLeftByLength(arrays, 1); + jest.spyOn( + helperArrayService, + 'getLeftByLength' + ).mockImplementation(() => result); + + expect(helperArrayService.getLeftByLength(arrays, 1)).toBe(result); + }); + }); + + describe('getRightByLength', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'getRightByLength'); + + helperArrayService.getRightByLength(arrays, 1); + expect(test).toHaveBeenCalledWith(arrays, 1); + }); + + it('should be success', async () => { + const result = helperArrayService.getRightByLength(arrays, 1); + jest.spyOn( + helperArrayService, + 'getRightByLength' + ).mockImplementation(() => result); + + expect(helperArrayService.getRightByLength(arrays, 1)).toBe(result); + }); + }); + + describe('getLast', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'getLast'); + + helperArrayService.getLast(arrays); + expect(test).toHaveBeenCalledWith(arrays); + }); + + it('should be success', async () => { + const result = helperArrayService.getLast(arrays); + jest.spyOn(helperArrayService, 'getLast').mockImplementation( + () => result + ); + + expect(helperArrayService.getLast(arrays)).toBe(result); + }); + }); + + describe('getFirst', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'getFirst'); + + helperArrayService.getFirst(arrays); + expect(test).toHaveBeenCalledWith(arrays); + }); + + it('should be success', async () => { + const result = helperArrayService.getFirst(arrays); + jest.spyOn(helperArrayService, 'getFirst').mockImplementation( + () => result + ); + + expect(helperArrayService.getFirst(arrays)).toBe(result); + }); + }); + + describe('getFirstIndexByValue', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'getFirstIndexByValue'); + + helperArrayService.getFirstIndexByValue(arrays, 1); + expect(test).toHaveBeenCalledWith(arrays, 1); + }); + + it('should be success', async () => { + const result = helperArrayService.getFirstIndexByValue(arrays, 1); + jest.spyOn( + helperArrayService, + 'getFirstIndexByValue' + ).mockImplementation(() => result); + + expect(helperArrayService.getFirstIndexByValue(arrays, 1)).toBe( + result + ); + }); + }); + + describe('getLastIndexByValue', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'getLastIndexByValue'); + + helperArrayService.getLastIndexByValue(arrays, 1); + expect(test).toHaveBeenCalledWith(arrays, 1); + }); + + it('should be success', async () => { + const result = helperArrayService.getLastIndexByValue(arrays, 1); + jest.spyOn( + helperArrayService, + 'getLastIndexByValue' + ).mockImplementation(() => result); + + expect(helperArrayService.getLastIndexByValue(arrays, 1)).toBe( + result + ); + }); + }); + + describe('removeByValue', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'removeByValue'); + + helperArrayService.removeByValue(arrays, 1); + expect(test).toHaveBeenCalledWith(arrays, 1); + }); + + it('should be success', async () => { + const result = helperArrayService.removeByValue(arrays, 1); + jest.spyOn(helperArrayService, 'removeByValue').mockImplementation( + () => result + ); + + expect(helperArrayService.removeByValue(arrays, 1)).toBe(result); + }); + }); + + describe('removeLeftByLength', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'removeLeftByLength'); + + helperArrayService.removeLeftByLength(arrays, 1); + expect(test).toHaveBeenCalledWith(arrays, 1); + }); + + it('should be success', async () => { + const result = helperArrayService.removeLeftByLength(arrays, 1); + jest.spyOn( + helperArrayService, + 'removeLeftByLength' + ).mockImplementation(() => result); + + expect(helperArrayService.removeLeftByLength(arrays, 1)).toBe( + result + ); + }); + }); + + describe('removeRightByLength', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'removeRightByLength'); + + helperArrayService.removeRightByLength(arrays, 1); + expect(test).toHaveBeenCalledWith(arrays, 1); + }); + + it('should be success', async () => { + const result = helperArrayService.removeRightByLength(arrays, 1); + jest.spyOn( + helperArrayService, + 'removeRightByLength' + ).mockImplementation(() => result); + + expect(helperArrayService.removeRightByLength(arrays, 1)).toBe( + result + ); + }); + }); + + describe('joinToString', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'joinToString'); + + helperArrayService.joinToString(arrays, ','); + expect(test).toHaveBeenCalledWith(arrays, ','); + }); + + it('should be success', async () => { + const result = helperArrayService.joinToString(arrays, ','); + jest.spyOn(helperArrayService, 'joinToString').mockImplementation( + () => result + ); + + expect(helperArrayService.joinToString(arrays, ',')).toBe(result); + }); + }); + + describe('reverse', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'reverse'); + + helperArrayService.reverse(arrays); + expect(test).toHaveBeenCalledWith(arrays); + }); + + it('should be success', async () => { + const result = helperArrayService.reverse(arrays); + jest.spyOn(helperArrayService, 'reverse').mockImplementation( + () => result + ); + + expect(helperArrayService.reverse(arrays)).toBe(result); + }); + }); + + describe('unique', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'unique'); + + helperArrayService.unique(arrays); + expect(test).toHaveBeenCalledWith(arrays); + }); + + it('should be success', async () => { + const result = helperArrayService.unique(arrays); + jest.spyOn(helperArrayService, 'unique').mockImplementation( + () => result + ); + + expect(helperArrayService.unique(arrays)).toBe(result); + }); + }); + + describe('shuffle', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'shuffle'); + + helperArrayService.shuffle(arrays); + expect(test).toHaveBeenCalledWith(arrays); + }); + + it('should be success', async () => { + const result = helperArrayService.shuffle(arrays); + jest.spyOn(helperArrayService, 'shuffle').mockImplementation( + () => result + ); + + expect(helperArrayService.shuffle(arrays)).toBe(result); + }); + }); + + describe('merge', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'merge'); + + helperArrayService.merge(arrays, arrays); + expect(test).toHaveBeenCalledWith(arrays, arrays); + }); + + it('should be success', async () => { + const result = helperArrayService.merge(arrays, arrays); + jest.spyOn(helperArrayService, 'merge').mockImplementation( + () => result + ); + + expect(helperArrayService.merge(arrays, arrays)).toBe(result); + }); + }); + + describe('mergeUnique', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'mergeUnique'); + + helperArrayService.mergeUnique(arrays, arrays); + expect(test).toHaveBeenCalledWith(arrays, arrays); + }); + + it('should be success', async () => { + const result = helperArrayService.mergeUnique(arrays, arrays); + jest.spyOn(helperArrayService, 'mergeUnique').mockImplementation( + () => result + ); + + expect(helperArrayService.mergeUnique(arrays, arrays)).toBe(result); + }); + }); + + describe('filterNotIncludeByValue', () => { + it('should be called', async () => { + const test = jest.spyOn( + helperArrayService, + 'filterNotIncludeByValue' + ); + + helperArrayService.filterNotIncludeByValue(arrays, 1); + expect(test).toHaveBeenCalledWith(arrays, 1); + }); + + it('should be success', async () => { + const result = helperArrayService.filterNotIncludeByValue( + arrays, + 1 + ); + jest.spyOn( + helperArrayService, + 'filterNotIncludeByValue' + ).mockImplementation(() => result); + + expect(helperArrayService.filterNotIncludeByValue(arrays, 1)).toBe( + result + ); + }); + }); + + describe('filterNotIncludeByArray', () => { + it('should be called', async () => { + const test = jest.spyOn( + helperArrayService, + 'filterNotIncludeByArray' + ); + + helperArrayService.filterNotIncludeByArray(arrays, arrays); + expect(test).toHaveBeenCalledWith(arrays, arrays); + }); + + it('should be success', async () => { + const result = helperArrayService.filterNotIncludeByArray( + arrays, + arrays + ); + jest.spyOn( + helperArrayService, + 'filterNotIncludeByArray' + ).mockImplementation(() => result); + + expect( + helperArrayService.filterNotIncludeByArray(arrays, arrays) + ).toBe(result); + }); + }); + + describe('filterIncludeByArray', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'filterIncludeByArray'); + + helperArrayService.filterIncludeByArray(arrays, arrays); + expect(test).toHaveBeenCalledWith(arrays, arrays); + }); + + it('should be success', async () => { + const result = helperArrayService.filterIncludeByArray( + arrays, + arrays + ); + jest.spyOn( + helperArrayService, + 'filterIncludeByArray' + ).mockImplementation(() => result); + + expect( + helperArrayService.filterIncludeByArray(arrays, arrays) + ).toBe(result); + }); + }); + + describe('equals', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'equals'); + + helperArrayService.equals(arrays, arrays); + expect(test).toHaveBeenCalledWith(arrays, arrays); + }); + + it('should be success', async () => { + const result = helperArrayService.equals(arrays, arrays); + jest.spyOn(helperArrayService, 'equals').mockImplementation( + () => result + ); + + expect(helperArrayService.equals(arrays, arrays)).toBe(result); + }); + }); + + describe('notEquals', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'notEquals'); + + helperArrayService.notEquals(arrays, arrays); + expect(test).toHaveBeenCalledWith(arrays, arrays); + }); + + it('should be success', async () => { + const result = helperArrayService.notEquals(arrays, arrays); + jest.spyOn(helperArrayService, 'notEquals').mockImplementation( + () => result + ); + + expect(helperArrayService.notEquals(arrays, arrays)).toBe(result); + }); + }); + + describe('in', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'in'); + + helperArrayService.in(arrays, arrays); + expect(test).toHaveBeenCalledWith(arrays, arrays); + }); + + it('should be success', async () => { + const result = helperArrayService.in(arrays, arrays); + jest.spyOn(helperArrayService, 'in').mockImplementation( + () => result + ); + + expect(helperArrayService.in(arrays, arrays)).toBe(result); + }); + }); + + describe('notIn', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'notIn'); + + helperArrayService.notIn(arrays, arrays); + expect(test).toHaveBeenCalledWith(arrays, arrays); + }); + + it('should be success', async () => { + const result = helperArrayService.notIn(arrays, arrays); + jest.spyOn(helperArrayService, 'notIn').mockImplementation( + () => result + ); + + expect(helperArrayService.notIn(arrays, arrays)).toBe(result); + }); + }); + + describe('includes', () => { + it('should be called', async () => { + const test = jest.spyOn(helperArrayService, 'includes'); + + helperArrayService.includes(arrays, 1); + expect(test).toHaveBeenCalledWith(arrays, 1); + }); + + it('should be success', async () => { + const result = helperArrayService.includes(arrays, 1); + jest.spyOn(helperArrayService, 'includes').mockImplementation( + () => result + ); + + expect(helperArrayService.includes(arrays, 1)).toBe(result); + }); + }); +}); diff --git a/packages/auth-api/test/helper/helper.date.service.spec.ts b/packages/auth-api/test/helper/helper.date.service.spec.ts new file mode 100644 index 0000000..d75cb17 --- /dev/null +++ b/packages/auth-api/test/helper/helper.date.service.spec.ts @@ -0,0 +1,839 @@ +import { ConfigService } from '@nestjs/config'; +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { + ENUM_HELPER_DATE_DIFF, + ENUM_HELPER_DATE_FORMAT, +} from 'src/utils/helper/helper.constant'; +import { HelperDateService } from 'src/utils/helper/service/helper.date.service'; + +describe('HelperDateService', () => { + let helperDateService: HelperDateService; + let configService: ConfigService; + const date1 = new Date(); + const date2 = new Date(); + let timezone: string; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + helperDateService = moduleRef.get(HelperDateService); + configService = moduleRef.get(ConfigService); + + timezone = configService.get('app.timezone'); + }); + + it('should be defined', () => { + expect(helperDateService).toBeDefined(); + }); + + describe('calculateAge', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'calculateAge'); + + helperDateService.calculateAge(date1); + expect(test).toHaveBeenCalledWith(date1); + }); + + it('should be success', async () => { + const result = helperDateService.calculateAge(date1); + jest.spyOn(helperDateService, 'calculateAge').mockImplementation( + () => result + ); + + expect(helperDateService.calculateAge(date1)).toBe(result); + }); + + it('should be success with options timezone', async () => { + const result = helperDateService.calculateAge(date1, { timezone }); + jest.spyOn(helperDateService, 'calculateAge').mockImplementation( + () => result + ); + + expect(helperDateService.calculateAge(date1, { timezone })).toBe( + result + ); + }); + }); + + describe('diff', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'diff'); + + helperDateService.diff(date1, date2); + expect(test).toHaveBeenCalledWith(date1, date2); + }); + + it('should be success', async () => { + const result = helperDateService.diff(date1, date2); + jest.spyOn(helperDateService, 'diff').mockImplementation( + () => result + ); + + expect(helperDateService.diff(date1, date2)).toBe(result); + }); + + it('should be success with options timezone', async () => { + const result = helperDateService.diff(date1, date2, { + timezone, + }); + jest.spyOn(helperDateService, 'diff').mockImplementation( + () => result + ); + + expect( + helperDateService.diff(date1, date2, { + timezone, + }) + ).toBe(result); + }); + + it('should be success with options format minutes', async () => { + const result = helperDateService.diff(date1, date2, { + format: ENUM_HELPER_DATE_DIFF.MINUTES, + }); + jest.spyOn(helperDateService, 'diff').mockImplementation( + () => result + ); + + expect( + helperDateService.diff(date1, date2, { + format: ENUM_HELPER_DATE_DIFF.MINUTES, + }) + ).toBe(result); + }); + + it('should be success with options format hours', async () => { + const result = helperDateService.diff(date1, date2, { + format: ENUM_HELPER_DATE_DIFF.HOURS, + }); + jest.spyOn(helperDateService, 'diff').mockImplementation( + () => result + ); + + expect( + helperDateService.diff(date1, date2, { + format: ENUM_HELPER_DATE_DIFF.HOURS, + }) + ).toBe(result); + }); + + it('should be success with options format days', async () => { + const result = helperDateService.diff(date1, date2, { + format: ENUM_HELPER_DATE_DIFF.DAYS, + }); + jest.spyOn(helperDateService, 'diff').mockImplementation( + () => result + ); + + expect( + helperDateService.diff(date1, date2, { + format: ENUM_HELPER_DATE_DIFF.DAYS, + }) + ).toBe(result); + }); + + it('should be success with options format seconds', async () => { + const result = helperDateService.diff(date1, date2, { + format: ENUM_HELPER_DATE_DIFF.SECONDS, + }); + jest.spyOn(helperDateService, 'diff').mockImplementation( + () => result + ); + + expect( + helperDateService.diff(date1, date2, { + format: ENUM_HELPER_DATE_DIFF.SECONDS, + }) + ).toBe(result); + }); + + it('should be success with options format milis', async () => { + const result = helperDateService.diff(date1, date2, { + format: ENUM_HELPER_DATE_DIFF.MILIS, + }); + jest.spyOn(helperDateService, 'diff').mockImplementation( + () => result + ); + + expect( + helperDateService.diff(date1, date2, { + format: ENUM_HELPER_DATE_DIFF.MILIS, + }) + ).toBe(result); + }); + }); + + describe('check', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'check'); + + helperDateService.check(date1.toISOString()); + expect(test).toHaveBeenCalledWith(date1.toISOString()); + }); + + it('should be success', async () => { + const result = helperDateService.check(date1.toISOString()); + jest.spyOn(helperDateService, 'check').mockImplementation( + () => result + ); + + expect(helperDateService.check(date1.toISOString())).toBe(result); + }); + + it('should be success with options timezone', async () => { + const result = helperDateService.check(date1.toISOString(), { + timezone, + }); + jest.spyOn(helperDateService, 'check').mockImplementation( + () => result + ); + + expect( + helperDateService.check(date1.toISOString(), { timezone }) + ).toBe(result); + }); + }); + + describe('checkTimezone', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'checkTimezone'); + + helperDateService.checkTimezone(timezone); + expect(test).toHaveBeenCalledWith(timezone); + }); + + it('should be success', async () => { + const result = helperDateService.checkTimezone(timezone); + jest.spyOn(helperDateService, 'checkTimezone').mockImplementation( + () => result + ); + + expect(helperDateService.checkTimezone(timezone)).toBe(result); + }); + }); + + describe('create', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'create'); + + helperDateService.create({ date: date1 }); + expect(test).toHaveBeenCalledWith({ date: date1 }); + }); + + it('should be success', async () => { + const result = helperDateService.create(); + jest.spyOn(helperDateService, 'create').mockImplementation( + () => result + ); + + expect(helperDateService.create()).toBe(result); + }); + + it('should be success with options date', async () => { + const result = helperDateService.create({ date: date1 }); + jest.spyOn(helperDateService, 'create').mockImplementation( + () => result + ); + + expect(helperDateService.create({ date: date1 })).toBe(result); + }); + + it('should be success with options date and timezone', async () => { + const result = helperDateService.create({ date: date1, timezone }); + jest.spyOn(helperDateService, 'create').mockImplementation( + () => result + ); + + expect(helperDateService.create({ date: date1, timezone })).toBe( + result + ); + }); + }); + + describe('timestamp', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'timestamp'); + + helperDateService.timestamp({ date: date1 }); + expect(test).toHaveBeenCalledWith({ date: date1 }); + }); + + it('should be success', async () => { + const result = helperDateService.timestamp(); + jest.spyOn(helperDateService, 'timestamp').mockImplementation( + () => result + ); + + expect(helperDateService.timestamp()).toBe(result); + }); + + it('should be success with options date', async () => { + const result = helperDateService.timestamp({ date: date1 }); + jest.spyOn(helperDateService, 'timestamp').mockImplementation( + () => result + ); + + expect(helperDateService.timestamp({ date: date1 })).toBe(result); + }); + + it('should be success with date and timezone', async () => { + const result = helperDateService.timestamp({ + date: date1, + timezone, + }); + jest.spyOn(helperDateService, 'timestamp').mockImplementation( + () => result + ); + + expect(helperDateService.timestamp({ date: date1, timezone })).toBe( + result + ); + }); + }); + + describe('format', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'format'); + + helperDateService.format(date1); + expect(test).toHaveBeenCalledWith(date1); + }); + + it('should be success', async () => { + const result = helperDateService.format(date1); + jest.spyOn(helperDateService, 'format').mockImplementation( + () => result + ); + + expect(helperDateService.format(date1)).toBe(result); + }); + + it('should be success with options format', async () => { + const result = helperDateService.format(date1, { + timezone: 'ASIA/JAKARTA', + }); + jest.spyOn(helperDateService, 'format').mockImplementation( + () => result + ); + + expect( + helperDateService.format(date1, { + timezone: 'ASIA/JAKARTA', + }) + ).toBe(result); + }); + + it('should be success with options timezone and format', async () => { + const result = helperDateService.format(date1, { + timezone: 'ASIA/JAKARTA', + format: ENUM_HELPER_DATE_FORMAT.DATE, + }); + jest.spyOn(helperDateService, 'format').mockImplementation( + () => result + ); + + expect( + helperDateService.format(date1, { + timezone: 'ASIA/JAKARTA', + format: ENUM_HELPER_DATE_FORMAT.DATE, + }) + ).toBe(result); + }); + }); + + describe('forwardInMinutes', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'forwardInMinutes'); + + helperDateService.forwardInMinutes(2); + expect(test).toHaveBeenCalledWith(2); + }); + + it('should be success', async () => { + const result = helperDateService.forwardInMinutes(2); + jest.spyOn( + helperDateService, + 'forwardInMinutes' + ).mockImplementation(() => result); + + expect(helperDateService.forwardInMinutes(2)).toBe(result); + }); + + it('should be success with options fromDate', async () => { + const result = helperDateService.forwardInMinutes(2, { + fromDate: date1, + }); + jest.spyOn( + helperDateService, + 'forwardInMinutes' + ).mockImplementation(() => result); + + expect( + helperDateService.forwardInMinutes(2, { fromDate: date1 }) + ).toBe(result); + }); + + it('should be success with options fromDate and timezone', async () => { + const result = helperDateService.forwardInMinutes(2, { + fromDate: date1, + timezone, + }); + jest.spyOn( + helperDateService, + 'forwardInMinutes' + ).mockImplementation(() => result); + + expect( + helperDateService.forwardInMinutes(2, { + fromDate: date1, + timezone, + }) + ).toBe(result); + }); + }); + + describe('backwardInMinutes', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'backwardInMinutes'); + + helperDateService.backwardInMinutes(2); + expect(test).toHaveBeenCalledWith(2); + }); + + it('should be success', async () => { + const result = helperDateService.backwardInMinutes(2); + jest.spyOn( + helperDateService, + 'backwardInMinutes' + ).mockImplementation(() => result); + + expect(helperDateService.backwardInMinutes(2)).toBe(result); + }); + + it('should be success with options fromDate', async () => { + const result = helperDateService.backwardInMinutes(2, { + fromDate: date1, + }); + jest.spyOn( + helperDateService, + 'backwardInMinutes' + ).mockImplementation(() => result); + + expect( + helperDateService.backwardInMinutes(2, { fromDate: date1 }) + ).toBe(result); + }); + + it('should be success with options fromDate and timezone', async () => { + const result = helperDateService.backwardInMinutes(2, { + fromDate: date1, + timezone, + }); + jest.spyOn( + helperDateService, + 'backwardInMinutes' + ).mockImplementation(() => result); + + expect( + helperDateService.backwardInMinutes(2, { + fromDate: date1, + timezone, + }) + ).toBe(result); + }); + }); + + describe('forwardInDays', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'forwardInDays'); + + helperDateService.forwardInDays(2); + expect(test).toHaveBeenCalledWith(2); + }); + + it('should be success', async () => { + const result = helperDateService.forwardInDays(2); + jest.spyOn(helperDateService, 'forwardInDays').mockImplementation( + () => result + ); + + expect(helperDateService.forwardInDays(2)).toBe(result); + }); + + it('should be success with options fromDate', async () => { + const result = helperDateService.forwardInDays(2, { + fromDate: date1, + }); + jest.spyOn(helperDateService, 'forwardInDays').mockImplementation( + () => result + ); + + expect( + helperDateService.forwardInDays(2, { fromDate: date1 }) + ).toBe(result); + }); + + it('should be success with options fromDate and timezone', async () => { + const result = helperDateService.forwardInDays(2, { + fromDate: date1, + timezone, + }); + jest.spyOn(helperDateService, 'forwardInDays').mockImplementation( + () => result + ); + + expect( + helperDateService.forwardInDays(2, { + fromDate: date1, + timezone, + }) + ).toBe(result); + }); + }); + + describe('backwardInDays', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'backwardInDays'); + + helperDateService.backwardInDays(2); + expect(test).toHaveBeenCalledWith(2); + }); + + it('should be success', async () => { + const result = helperDateService.backwardInDays(2); + jest.spyOn(helperDateService, 'backwardInDays').mockImplementation( + () => result + ); + + expect(helperDateService.backwardInDays(2)).toBe(result); + }); + + it('should be success with options fromDate', async () => { + const result = helperDateService.backwardInDays(2, { + fromDate: date1, + }); + jest.spyOn(helperDateService, 'backwardInDays').mockImplementation( + () => result + ); + + expect( + helperDateService.backwardInDays(2, { fromDate: date1 }) + ).toBe(result); + }); + + it('should be success with options fromDate and timezone', async () => { + const result = helperDateService.backwardInDays(2, { + fromDate: date1, + timezone, + }); + jest.spyOn(helperDateService, 'backwardInDays').mockImplementation( + () => result + ); + + expect( + helperDateService.backwardInDays(2, { + fromDate: date1, + timezone, + }) + ).toBe(result); + }); + }); + + describe('forwardInMonths', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'forwardInMonths'); + + helperDateService.forwardInMonths(2); + expect(test).toHaveBeenCalledWith(2); + }); + + it('should be success', async () => { + const result = helperDateService.forwardInMonths(2); + jest.spyOn(helperDateService, 'forwardInMonths').mockImplementation( + () => result + ); + + expect(helperDateService.forwardInMonths(2)).toBe(result); + }); + + it('should be success with options fromDate', async () => { + const result = helperDateService.forwardInMonths(2, { + fromDate: date1, + }); + jest.spyOn(helperDateService, 'forwardInMonths').mockImplementation( + () => result + ); + + expect( + helperDateService.forwardInMonths(2, { fromDate: date1 }) + ).toBe(result); + }); + + it('should be success with options fromDate and timezone', async () => { + const result = helperDateService.forwardInMonths(2, { + fromDate: date1, + timezone, + }); + jest.spyOn(helperDateService, 'forwardInMonths').mockImplementation( + () => result + ); + + expect( + helperDateService.forwardInMonths(2, { + fromDate: date1, + timezone, + }) + ).toBe(result); + }); + }); + + describe('backwardInMonths', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'backwardInMonths'); + + helperDateService.backwardInMonths(2); + expect(test).toHaveBeenCalledWith(2); + }); + + it('should be success', async () => { + const result = helperDateService.backwardInMonths(2); + jest.spyOn( + helperDateService, + 'backwardInMonths' + ).mockImplementation(() => result); + + expect(helperDateService.backwardInMonths(2)).toBe(result); + }); + + it('should be success with options fromDate', async () => { + const result = helperDateService.backwardInMonths(2, { + fromDate: date1, + }); + jest.spyOn( + helperDateService, + 'backwardInMonths' + ).mockImplementation(() => result); + + expect( + helperDateService.backwardInMonths(2, { fromDate: date1 }) + ).toBe(result); + }); + + it('should be success with options fromDate and timezone', async () => { + const result = helperDateService.backwardInMonths(2, { + fromDate: date1, + timezone, + }); + jest.spyOn( + helperDateService, + 'backwardInMonths' + ).mockImplementation(() => result); + + expect( + helperDateService.backwardInMonths(2, { + fromDate: date1, + timezone, + }) + ).toBe(result); + }); + }); + + describe('endOfMonth', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'endOfMonth'); + + helperDateService.endOfMonth(2); + expect(test).toHaveBeenCalledWith(2); + }); + + it('should be success', async () => { + const result = helperDateService.endOfMonth(2); + jest.spyOn(helperDateService, 'endOfMonth').mockImplementation( + () => result + ); + + expect(helperDateService.endOfMonth(2)).toBe(result); + }); + + it('should be success with options year', async () => { + const result = helperDateService.endOfMonth(2, { + year: 1999, + }); + jest.spyOn(helperDateService, 'endOfMonth').mockImplementation( + () => result + ); + + expect( + helperDateService.endOfMonth(2, { + year: 1999, + }) + ).toBe(result); + }); + + it('should be success with options timezone', async () => { + const result = helperDateService.endOfMonth(2, { + timezone, + }); + jest.spyOn(helperDateService, 'endOfMonth').mockImplementation( + () => result + ); + + expect( + helperDateService.endOfMonth(2, { + timezone, + }) + ).toBe(result); + }); + + it('should be success with options year and timezone', async () => { + const result = helperDateService.endOfMonth(2, { + year: 1999, + timezone, + }); + jest.spyOn(helperDateService, 'endOfMonth').mockImplementation( + () => result + ); + + expect( + helperDateService.endOfMonth(2, { + year: 1999, + timezone, + }) + ).toBe(result); + }); + }); + + describe('startOfMonth', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'startOfMonth'); + + helperDateService.startOfMonth(2); + expect(test).toHaveBeenCalledWith(2); + }); + + it('should be success', async () => { + const result = helperDateService.startOfMonth(2); + jest.spyOn(helperDateService, 'startOfMonth').mockImplementation( + () => result + ); + + expect(helperDateService.startOfMonth(2)).toBe(result); + }); + + it('should be success with options year', async () => { + const result = helperDateService.startOfMonth(2, { + year: 1999, + }); + jest.spyOn(helperDateService, 'startOfMonth').mockImplementation( + () => result + ); + + expect( + helperDateService.startOfMonth(2, { + year: 1999, + }) + ).toBe(result); + }); + + it('should be success with options timezone', async () => { + const result = helperDateService.startOfMonth(2, { + timezone, + }); + jest.spyOn(helperDateService, 'startOfMonth').mockImplementation( + () => result + ); + + expect( + helperDateService.startOfMonth(2, { + timezone, + }) + ).toBe(result); + }); + + it('should be success with options year and timezone', async () => { + const result = helperDateService.startOfMonth(2, { + year: 1999, + timezone, + }); + jest.spyOn(helperDateService, 'startOfMonth').mockImplementation( + () => result + ); + + expect( + helperDateService.startOfMonth(2, { + year: 1999, + timezone, + }) + ).toBe(result); + }); + }); + + describe('endOfYear', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'endOfYear'); + + helperDateService.endOfYear(1999); + expect(test).toHaveBeenCalledWith(1999); + }); + + it('should be success', async () => { + const result = helperDateService.endOfYear(1999); + jest.spyOn(helperDateService, 'endOfYear').mockImplementation( + () => result + ); + + expect(helperDateService.endOfYear(1999)).toBe(result); + }); + + it('should be success with options timezone', async () => { + const result = helperDateService.endOfYear(1999, { + timezone, + }); + jest.spyOn(helperDateService, 'endOfYear').mockImplementation( + () => result + ); + + expect( + helperDateService.endOfYear(1999, { + timezone, + }) + ).toBe(result); + }); + }); + + describe('startOfYear', () => { + it('should be called', async () => { + const test = jest.spyOn(helperDateService, 'startOfYear'); + + helperDateService.startOfYear(1999); + expect(test).toHaveBeenCalledWith(1999); + }); + + it('should be success', async () => { + const result = helperDateService.startOfYear(1999); + jest.spyOn(helperDateService, 'startOfYear').mockImplementation( + () => result + ); + + expect(helperDateService.startOfYear(1999)).toBe(result); + }); + + it('should be success with options timezone', async () => { + const result = helperDateService.startOfYear(1999, { + timezone, + }); + jest.spyOn(helperDateService, 'startOfYear').mockImplementation( + () => result + ); + + expect( + helperDateService.startOfYear(1999, { + timezone, + }) + ).toBe(result); + }); + }); +}); diff --git a/packages/auth-api/test/helper/helper.encryption.service.spec.ts b/packages/auth-api/test/helper/helper.encryption.service.spec.ts new file mode 100644 index 0000000..c068149 --- /dev/null +++ b/packages/auth-api/test/helper/helper.encryption.service.spec.ts @@ -0,0 +1,281 @@ +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { HelperEncryptionService } from 'src/utils/helper/service/helper.encryption.service'; + +describe('HelperEncryptionService', () => { + let helperEncryptionService: HelperEncryptionService; + const data = 'aaaa'; + const dataObject = { test: 'aaaa' }; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + helperEncryptionService = moduleRef.get( + HelperEncryptionService + ); + }); + + it('should be defined', () => { + expect(helperEncryptionService).toBeDefined(); + }); + + describe('base64Encrypt', () => { + it('should be called', async () => { + const test = jest.spyOn(helperEncryptionService, 'base64Encrypt'); + + helperEncryptionService.base64Encrypt(data); + expect(test).toHaveBeenCalledWith(data); + }); + + it('should be success', async () => { + const result = helperEncryptionService.base64Encrypt(data); + jest.spyOn( + helperEncryptionService, + 'base64Encrypt' + ).mockImplementation(() => result); + + expect(helperEncryptionService.base64Encrypt(data)).toBe(result); + }); + }); + + describe('base64Decrypt', () => { + it('should be called', async () => { + const test = jest.spyOn(helperEncryptionService, 'base64Decrypt'); + + const result = helperEncryptionService.base64Encrypt(data); + helperEncryptionService.base64Decrypt(result); + expect(test).toHaveBeenCalledWith(result); + }); + + it('should be success', async () => { + const result = helperEncryptionService.base64Encrypt(data); + jest.spyOn( + helperEncryptionService, + 'base64Decrypt' + ).mockImplementation(() => data); + + expect(helperEncryptionService.base64Decrypt(result)).toBe(data); + }); + }); + + describe('base64Compare', () => { + it('should be called', async () => { + const test = jest.spyOn(helperEncryptionService, 'base64Compare'); + + helperEncryptionService.base64Compare(data, data); + expect(test).toHaveBeenCalledWith(data, data); + }); + + it('should be success', async () => { + const result = helperEncryptionService.base64Compare(data, data); + jest.spyOn( + helperEncryptionService, + 'base64Compare' + ).mockImplementation(() => result); + + expect(helperEncryptionService.base64Compare(data, data)).toBe( + result + ); + }); + }); + + describe('aes256Encrypt', () => { + it('should be called', async () => { + const test = jest.spyOn(helperEncryptionService, 'aes256Encrypt'); + + helperEncryptionService.aes256Encrypt( + data, + '1234567', + '1231231231231231' + ); + expect(test).toHaveBeenCalledWith( + data, + '1234567', + '1231231231231231' + ); + }); + + it('string should be success', async () => { + const result = helperEncryptionService.aes256Encrypt( + data, + '1234567', + '1231231231231231' + ); + jest.spyOn( + helperEncryptionService, + 'aes256Encrypt' + ).mockImplementation(() => result); + + expect( + helperEncryptionService.aes256Encrypt( + data, + '1234567', + '1231231231231231' + ) + ).toBe(result); + }); + + it('object should be success', async () => { + const result = helperEncryptionService.aes256Encrypt( + dataObject, + '1234567', + '1231231231231231' + ); + jest.spyOn( + helperEncryptionService, + 'aes256Encrypt' + ).mockImplementation(() => result); + + expect( + helperEncryptionService.aes256Encrypt( + dataObject, + '1234567', + '1231231231231231' + ) + ).toBe(result); + }); + }); + + describe('aes256Decrypt', () => { + it('should be called', async () => { + const test = jest.spyOn(helperEncryptionService, 'aes256Decrypt'); + + const result = helperEncryptionService.aes256Encrypt( + data, + '1234567', + '1231231231231231' + ); + helperEncryptionService.aes256Decrypt( + result, + '1234567', + '1231231231231231' + ); + expect(test).toHaveBeenCalledWith( + result, + '1234567', + '1231231231231231' + ); + }); + + it('should be success', async () => { + const result = helperEncryptionService.aes256Encrypt( + data, + '1234567', + '1231231231231231' + ); + jest.spyOn( + helperEncryptionService, + 'aes256Decrypt' + ).mockImplementation(() => data); + + expect( + helperEncryptionService.aes256Decrypt( + result, + '1234567', + '1231231231231231' + ) + ).toBe(data); + }); + }); + + describe('jwtEncrypt', () => { + it('should be called', async () => { + const test = jest.spyOn(helperEncryptionService, 'jwtEncrypt'); + + helperEncryptionService.jwtEncrypt( + { data }, + { expiredIn: '1h', secretKey: data } + ); + expect(test).toHaveBeenCalledWith( + { data }, + { expiredIn: '1h', secretKey: data } + ); + }); + + it('should be success', async () => { + const result = helperEncryptionService.jwtEncrypt( + { data }, + { expiredIn: '1h', secretKey: data } + ); + jest.spyOn( + helperEncryptionService, + 'jwtEncrypt' + ).mockImplementation(() => result); + + expect( + helperEncryptionService.jwtEncrypt( + { data }, + { expiredIn: '1h', secretKey: data } + ) + ).toBe(result); + }); + }); + + describe('jwtDecrypt', () => { + it('should be called', async () => { + const test = jest.spyOn(helperEncryptionService, 'jwtDecrypt'); + + const result = helperEncryptionService.jwtEncrypt( + { data }, + { expiredIn: '1h', secretKey: data } + ); + helperEncryptionService.jwtDecrypt(result); + expect(test).toHaveBeenCalledWith(result); + }); + + it('should be success', async () => { + const result = helperEncryptionService.jwtEncrypt( + { data }, + { expiredIn: '1h', secretKey: data } + ); + const decrypt = helperEncryptionService.jwtDecrypt(result); + jest.spyOn( + helperEncryptionService, + 'jwtDecrypt' + ).mockImplementation(() => decrypt); + + expect(helperEncryptionService.jwtDecrypt(result)).toBe(decrypt); + }); + }); + + describe('jwtVerify', () => { + it('should be called', async () => { + const test = jest.spyOn(helperEncryptionService, 'jwtVerify'); + + const result = await helperEncryptionService.jwtEncrypt( + { data }, + { expiredIn: '1h', secretKey: data } + ); + helperEncryptionService.jwtVerify(result); + expect(test).toHaveBeenCalledWith(result); + }); + + it('should be success', async () => { + const result = helperEncryptionService.jwtEncrypt( + { data }, + { expiredIn: '1h', secretKey: data } + ); + const verify = helperEncryptionService.jwtVerify(result); + jest.spyOn(helperEncryptionService, 'jwtVerify').mockImplementation( + () => verify + ); + + expect(helperEncryptionService.jwtVerify(result)).toBe(verify); + }); + + it('should be failed', async () => { + const result = helperEncryptionService.jwtEncrypt( + { data }, + { expiredIn: '1h', secretKey: data } + ); + const verify = helperEncryptionService.jwtVerify(result); + jest.spyOn(helperEncryptionService, 'jwtVerify').mockImplementation( + () => verify + ); + + expect(helperEncryptionService.jwtVerify(result)).toBe(verify); + }); + }); +}); diff --git a/packages/auth-api/test/helper/helper.file.service.spec.ts b/packages/auth-api/test/helper/helper.file.service.spec.ts new file mode 100644 index 0000000..d390b67 --- /dev/null +++ b/packages/auth-api/test/helper/helper.file.service.spec.ts @@ -0,0 +1,37 @@ +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { HelperFileService } from 'src/utils/helper/service/helper.file.service'; + +describe('HelperFileService', () => { + let helperFileService: HelperFileService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + helperFileService = moduleRef.get(HelperFileService); + }); + + it('should be defined', () => { + expect(helperFileService).toBeDefined(); + }); + + describe('writeExcel', () => { + it('should be called', async () => { + const test = jest.spyOn(helperFileService, 'writeExcel'); + + helperFileService.writeExcel([], []); + expect(test).toHaveBeenCalledWith([], []); + }); + + it('should be success', async () => { + const result = helperFileService.writeExcel([], []); + jest.spyOn(helperFileService, 'writeExcel').mockImplementation( + () => result + ); + + expect(helperFileService.writeExcel([], [])).toBe(result); + }); + }); +}); diff --git a/packages/auth-api/test/helper/helper.geo.service.spec.ts b/packages/auth-api/test/helper/helper.geo.service.spec.ts new file mode 100644 index 0000000..d6b0761 --- /dev/null +++ b/packages/auth-api/test/helper/helper.geo.service.spec.ts @@ -0,0 +1,59 @@ +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { HelperGeoService } from 'src/utils/helper/service/helper.geo.service'; + +describe('HelperGeoService', () => { + let helperGeoService: HelperGeoService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + helperGeoService = moduleRef.get(HelperGeoService); + }); + + it('should be defined', () => { + expect(HelperGeoService).toBeDefined(); + }); + + describe('inRadius', () => { + it('should be called', async () => { + const test = jest.spyOn(helperGeoService, 'inRadius'); + + helperGeoService.inRadius( + { longitude: 6.1754, latitude: 106.8272, radiusInMeters: 10 }, + { longitude: 6.1754, latitude: 106.8272 } + ); + expect(test).toHaveBeenCalledWith( + { longitude: 6.1754, latitude: 106.8272, radiusInMeters: 10 }, + { longitude: 6.1754, latitude: 106.8272 } + ); + }); + + it('should be success', async () => { + const result = helperGeoService.inRadius( + { + longitude: 6.1754, + latitude: 106.8272, + radiusInMeters: 10, + }, + { longitude: 6.1754, latitude: 106.8272 } + ); + jest.spyOn(helperGeoService, 'inRadius').mockImplementation( + () => result + ); + + expect( + helperGeoService.inRadius( + { + longitude: 6.1754, + latitude: 106.8272, + radiusInMeters: 10, + }, + { longitude: 6.1754, latitude: 106.8272 } + ) + ).toBe(result); + }); + }); +}); diff --git a/packages/auth-api/test/helper/helper.hash.service.spec.ts b/packages/auth-api/test/helper/helper.hash.service.spec.ts new file mode 100644 index 0000000..a033ec6 --- /dev/null +++ b/packages/auth-api/test/helper/helper.hash.service.spec.ts @@ -0,0 +1,122 @@ +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { HelperHashService } from 'src/utils/helper/service/helper.hash.service'; + +describe('HelperHashService', () => { + let helperHashService: HelperHashService; + const data = 'aaaa'; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + helperHashService = moduleRef.get(HelperHashService); + }); + + it('should be defined', () => { + expect(helperHashService).toBeDefined(); + }); + + describe('randomSalt', () => { + it('should be called', async () => { + const test = jest.spyOn(helperHashService, 'randomSalt'); + + helperHashService.randomSalt(10); + expect(test).toHaveBeenCalled(); + }); + + it('should be success', async () => { + const result = helperHashService.randomSalt(10); + jest.spyOn(helperHashService, 'randomSalt').mockImplementation( + () => result + ); + + expect(helperHashService.randomSalt(10)).toBe(result); + }); + }); + + describe('bcrypt', () => { + it('should be called', async () => { + const test = jest.spyOn(helperHashService, 'bcrypt'); + + const salt = helperHashService.randomSalt(10); + helperHashService.bcrypt(data, salt); + expect(test).toHaveBeenCalledWith(data, salt); + }); + + it('should be success', async () => { + const salt = helperHashService.randomSalt(10); + const result = helperHashService.bcrypt(data, salt); + jest.spyOn(helperHashService, 'bcrypt').mockImplementation( + () => result + ); + + expect(helperHashService.bcrypt(data, salt)).toBe(result); + }); + }); + + describe('bcryptCompare', () => { + it('should be called', async () => { + const test = jest.spyOn(helperHashService, 'bcryptCompare'); + + const salt = helperHashService.randomSalt(10); + const hash = helperHashService.bcrypt(data, salt); + helperHashService.bcryptCompare('bbbb', hash); + expect(test).toHaveBeenCalledWith('bbbb', hash); + }); + + it('should be success', async () => { + const salt = helperHashService.randomSalt(10); + const hash = helperHashService.bcrypt(data, salt); + const validate = helperHashService.bcryptCompare('bbbb', hash); + jest.spyOn(helperHashService, 'bcryptCompare').mockImplementation( + () => validate + ); + + expect(helperHashService.bcryptCompare('bbbb', hash)).toBe( + validate + ); + }); + }); + + describe('sha256', () => { + it('should be called', async () => { + const test = jest.spyOn(helperHashService, 'sha256'); + + helperHashService.sha256(data); + expect(test).toHaveBeenCalledWith(data); + }); + + it('should be success', async () => { + const hash = helperHashService.sha256(data); + jest.spyOn(helperHashService, 'sha256').mockImplementation( + () => hash + ); + + expect(helperHashService.sha256(data)).toBe(hash); + }); + }); + + describe('sha256Compare', () => { + it('should be called', async () => { + const test = jest.spyOn(helperHashService, 'sha256Compare'); + + const hash = helperHashService.sha256(data); + helperHashService.sha256Compare('bbbb', hash); + expect(test).toHaveBeenCalledWith('bbbb', hash); + }); + + it('should be success', async () => { + const hash = helperHashService.sha256(data); + const validate = helperHashService.sha256Compare('bbbb', hash); + jest.spyOn(helperHashService, 'bcryptCompare').mockImplementation( + () => validate + ); + + expect(helperHashService.sha256Compare('bbbb', hash)).toBe( + validate + ); + }); + }); +}); diff --git a/packages/auth-api/test/helper/helper.number.service.spec.ts b/packages/auth-api/test/helper/helper.number.service.spec.ts new file mode 100644 index 0000000..d2b331e --- /dev/null +++ b/packages/auth-api/test/helper/helper.number.service.spec.ts @@ -0,0 +1,92 @@ +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { HelperNumberService } from 'src/utils/helper/service/helper.number.service'; + +describe('HelperNumberService', () => { + let helperNumberService: HelperNumberService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + helperNumberService = + moduleRef.get(HelperNumberService); + }); + + it('should be defined', () => { + expect(helperNumberService).toBeDefined(); + }); + + describe('check', () => { + it('should be called', async () => { + const test = jest.spyOn(helperNumberService, 'check'); + + helperNumberService.check('111'); + expect(test).toHaveBeenCalledWith('111'); + }); + + it('should be success', async () => { + const result = helperNumberService.check('111'); + jest.spyOn(helperNumberService, 'check').mockImplementation( + () => result + ); + + expect(helperNumberService.check('111')).toBe(result); + }); + }); + + describe('convert', () => { + it('should be called', async () => { + const test = jest.spyOn(helperNumberService, 'convert'); + + helperNumberService.convert('111'); + expect(test).toHaveBeenCalledWith('111'); + }); + + it('should be success', async () => { + const result = helperNumberService.convert('111'); + jest.spyOn(helperNumberService, 'convert').mockImplementation( + () => result + ); + + expect(helperNumberService.convert('111')).toBe(result); + }); + }); + + describe('random', () => { + it('should be called', async () => { + const test = jest.spyOn(helperNumberService, 'random'); + + helperNumberService.random(10); + expect(test).toHaveBeenCalledWith(10); + }); + + it('should be success', async () => { + const result = helperNumberService.random(10); + jest.spyOn(helperNumberService, 'random').mockImplementation( + () => result + ); + + expect(helperNumberService.random(10)).toBe(result); + }); + }); + + describe('randomInRange', () => { + it('should be called', async () => { + const test = jest.spyOn(helperNumberService, 'randomInRange'); + + helperNumberService.randomInRange(5, 8); + expect(test).toHaveBeenCalledWith(5, 8); + }); + + it('should be success', async () => { + const result = helperNumberService.randomInRange(5, 8); + jest.spyOn(helperNumberService, 'randomInRange').mockImplementation( + () => result + ); + + expect(helperNumberService.randomInRange(5, 8)).toBe(result); + }); + }); +}); diff --git a/packages/auth-api/test/helper/helper.service.spec.ts b/packages/auth-api/test/helper/helper.service.spec.ts new file mode 100644 index 0000000..d897842 --- /dev/null +++ b/packages/auth-api/test/helper/helper.service.spec.ts @@ -0,0 +1,37 @@ +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { HelperService } from 'src/utils/helper/service/helper.service'; + +describe('HelperService', () => { + let helperService: HelperService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + helperService = moduleRef.get(HelperService); + }); + + it('should be defined', () => { + expect(helperService).toBeDefined(); + }); + + describe('check', () => { + it('should be called', async () => { + const test = jest.spyOn(helperService, 'delay'); + + await helperService.delay(100); + expect(test).toHaveBeenCalledWith(100); + }); + + it('should be success', async () => { + const result = await helperService.delay(100); + jest.spyOn(helperService, 'delay').mockImplementation( + async () => result + ); + + expect(await helperService.delay(100)).toBe(result); + }); + }); +}); diff --git a/packages/auth-api/test/helper/helper.string.service.spec.ts b/packages/auth-api/test/helper/helper.string.service.spec.ts new file mode 100644 index 0000000..eea4bc0 --- /dev/null +++ b/packages/auth-api/test/helper/helper.string.service.spec.ts @@ -0,0 +1,317 @@ +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { HelperStringService } from 'src/utils/helper/service/helper.string.service'; + +describe('HelperStringService', () => { + let helperStringService: HelperStringService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + helperStringService = + moduleRef.get(HelperStringService); + }); + + it('should be defined', () => { + expect(helperStringService).toBeDefined(); + }); + + describe('checkEmail', () => { + it('should be called', async () => { + const test = jest.spyOn(helperStringService, 'checkEmail'); + + helperStringService.checkEmail('111'); + expect(test).toHaveBeenCalledWith('111'); + }); + + it('should be success', async () => { + const result = helperStringService.checkEmail('111'); + jest.spyOn(helperStringService, 'checkEmail').mockImplementation( + () => result + ); + + expect(helperStringService.checkEmail('111')).toBe(result); + }); + }); + + describe('randomReference', () => { + it('should be called', async () => { + const test = jest.spyOn(helperStringService, 'randomReference'); + + helperStringService.randomReference(10); + expect(test).toHaveBeenCalledWith(10); + }); + + it('should be success', async () => { + const result = helperStringService.randomReference(10, 'test'); + jest.spyOn( + helperStringService, + 'randomReference' + ).mockImplementation(() => result); + + expect(helperStringService.randomReference(10, 'test')).toBe( + result + ); + }); + }); + + describe('random', () => { + it('should be called', async () => { + const test = jest.spyOn(helperStringService, 'random'); + + helperStringService.random(5); + expect(test).toHaveBeenCalledWith(5); + }); + + it('should be success', async () => { + const result = helperStringService.random(5); + jest.spyOn(helperStringService, 'random').mockImplementation( + () => result + ); + + expect(helperStringService.random(5)).toBe(result); + }); + + it('should be success with options prefix', async () => { + const result = helperStringService.random(5, { prefix: 'aaa' }); + jest.spyOn(helperStringService, 'random').mockImplementation( + () => result + ); + + expect(helperStringService.random(5, { prefix: 'aaa' })).toBe( + result + ); + }); + + it('should be success with options prefix and safe', async () => { + const result = helperStringService.random(5, { + prefix: 'aaa', + safe: true, + }); + jest.spyOn(helperStringService, 'random').mockImplementation( + () => result + ); + + expect( + helperStringService.random(5, { prefix: 'aaa', safe: true }) + ).toBe(result); + }); + }); + + describe('censor', () => { + it('should be called', async () => { + const test = jest.spyOn(helperStringService, 'censor'); + + helperStringService.censor('12312312'); + expect(test).toHaveBeenCalledWith('12312312'); + }); + + it('string length 1 should be success', async () => { + const result = helperStringService.censor('1'); + jest.spyOn(helperStringService, 'censor').mockImplementation( + () => result + ); + + expect(helperStringService.censor('1')).toBe(result); + }); + + it('string length 1 - 4 should be success', async () => { + const result = helperStringService.censor('125'); + jest.spyOn(helperStringService, 'censor').mockImplementation( + () => result + ); + + expect(helperStringService.censor('125')).toBe(result); + }); + + it('string length 4 - 10 should be success', async () => { + const result = helperStringService.censor('123245'); + jest.spyOn(helperStringService, 'censor').mockImplementation( + () => result + ); + + expect(helperStringService.censor('123245')).toBe(result); + }); + + it('string length > 10 should be success', async () => { + const result = helperStringService.censor( + '12312312312312312312312' + ); + jest.spyOn(helperStringService, 'censor').mockImplementation( + () => result + ); + + expect(helperStringService.censor('12312312312312312312312')).toBe( + result + ); + }); + }); + + describe('checkStringOrNumber', () => { + it('should be called', async () => { + const test = jest.spyOn(helperStringService, 'checkStringOrNumber'); + + helperStringService.checkStringOrNumber('123'); + expect(test).toHaveBeenCalledWith('123'); + }); + + it('should be success', async () => { + const result = helperStringService.checkStringOrNumber('123'); + jest.spyOn( + helperStringService, + 'checkStringOrNumber' + ).mockImplementation(() => result); + + expect(helperStringService.checkStringOrNumber('123')).toBe(result); + }); + }); + + describe('convertStringToNumberOrBooleanIfPossible', () => { + it('should be called', async () => { + const test = jest.spyOn( + helperStringService, + 'convertStringToNumberOrBooleanIfPossible' + ); + + helperStringService.convertStringToNumberOrBooleanIfPossible('123'); + expect(test).toHaveBeenCalledWith('123'); + }); + + it('should be success string', async () => { + const result = + helperStringService.convertStringToNumberOrBooleanIfPossible( + 'check' + ); + jest.spyOn( + helperStringService, + 'convertStringToNumberOrBooleanIfPossible' + ).mockImplementation(() => result); + + expect( + helperStringService.convertStringToNumberOrBooleanIfPossible( + 'check' + ) + ).toBe(result); + }); + + it('should be success boolean', async () => { + const result = + helperStringService.convertStringToNumberOrBooleanIfPossible( + 'true' + ); + jest.spyOn( + helperStringService, + 'convertStringToNumberOrBooleanIfPossible' + ).mockImplementation(() => result); + + expect( + helperStringService.convertStringToNumberOrBooleanIfPossible( + 'true' + ) + ).toBe(result); + }); + + it('should be success number', async () => { + const result = + helperStringService.convertStringToNumberOrBooleanIfPossible( + '123' + ); + jest.spyOn( + helperStringService, + 'convertStringToNumberOrBooleanIfPossible' + ).mockImplementation(() => result); + + expect( + helperStringService.convertStringToNumberOrBooleanIfPossible( + '123' + ) + ).toBe(result); + }); + }); + + describe('checkPasswordWeak', () => { + it('should be called', async () => { + const test = jest.spyOn(helperStringService, 'checkPasswordWeak'); + + helperStringService.checkPasswordWeak('aaAAbbBBccCC'); + expect(test).toHaveBeenCalledWith('aaAAbbBBccCC'); + }); + + it('should be success', async () => { + const result = + helperStringService.checkPasswordWeak('aaAAbbBBccCC'); + jest.spyOn( + helperStringService, + 'checkPasswordWeak' + ).mockImplementation(() => result); + + expect(helperStringService.checkPasswordWeak('aaAAbbBBccCC')).toBe( + result + ); + }); + }); + + describe('checkPasswordMedium', () => { + it('should be called', async () => { + const test = jest.spyOn(helperStringService, 'checkPasswordMedium'); + + helperStringService.checkPasswordMedium('aaAA12345'); + expect(test).toHaveBeenCalledWith('aaAA12345'); + }); + + it('should be success', async () => { + const result = helperStringService.checkPasswordMedium('aaAA12345'); + jest.spyOn( + helperStringService, + 'checkPasswordMedium' + ).mockImplementation(() => result); + + expect(helperStringService.checkPasswordMedium('aaAA12345')).toBe( + result + ); + }); + }); + + describe('checkPasswordStrong', () => { + it('should be called', async () => { + const test = jest.spyOn(helperStringService, 'checkPasswordStrong'); + + helperStringService.checkPasswordStrong('aaAA12345@!'); + expect(test).toHaveBeenCalledWith('aaAA12345@!'); + }); + + it('should be success', async () => { + const result = + helperStringService.checkPasswordStrong('aaAA12345@!'); + jest.spyOn( + helperStringService, + 'checkPasswordStrong' + ).mockImplementation(() => result); + + expect(helperStringService.checkPasswordStrong('aaAA12345@!')).toBe( + result + ); + }); + }); + + describe('checkSafeString', () => { + it('should be called', async () => { + const test = jest.spyOn(helperStringService, 'checkSafeString'); + + helperStringService.checkSafeString('123'); + expect(test).toHaveBeenCalledWith('123'); + }); + + it('should be success', async () => { + const result = helperStringService.checkSafeString('123'); + jest.spyOn( + helperStringService, + 'checkSafeString' + ).mockImplementation(() => result); + + expect(helperStringService.checkSafeString('123')).toBe(result); + }); + }); +}); diff --git a/packages/auth-api/test/jest.json b/packages/auth-api/test/jest.json new file mode 100644 index 0000000..4cdec08 --- /dev/null +++ b/packages/auth-api/test/jest.json @@ -0,0 +1,41 @@ +{ + "testTimeout": 5000, + "rootDir": "..", + "modulePaths": [ + "." + ], + "testEnvironment": "node", + "testMatch": [ + "/test/**/*.spec.ts" + ], + "collectCoverage": true, + "coverageDirectory": "coverage", + "collectCoverageFrom": [ + "./src/auth/service/**", + "./src/cache/service/**", + "./src/config/service/**", + "./src/database/service/**", + "./src/debugger/service/**", + "./src/logger/service/**", + "./src/message/service/**", + "./src/pagination/service/**", + "./src/setting/service/**", + "./src/utils/helper/service/**" + ], + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": 100 + } + }, + "moduleFileExtensions": [ + "js", + "ts", + "json" + ], + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/packages/auth-api/test/logger/logger.service.spec.ts b/packages/auth-api/test/logger/logger.service.spec.ts new file mode 100644 index 0000000..4f7d97a --- /dev/null +++ b/packages/auth-api/test/logger/logger.service.spec.ts @@ -0,0 +1,139 @@ +import { Test } from '@nestjs/testing'; +import { Types } from 'mongoose'; +import { CoreModule } from 'src/core/core.module'; +import { ENUM_LOGGER_ACTION } from 'src/logger/logger.constant'; +import { ILogger } from 'src/logger/logger.interface'; +import { LoggerService } from 'src/logger/service/logger.service'; +import { ENUM_REQUEST_METHOD } from 'src/utils/request/request.constant'; +import { v4 } from 'uuid'; + +describe('LoggerService', () => { + let loggerService: LoggerService; + const logger: ILogger = { + action: ENUM_LOGGER_ACTION.TEST, + description: 'test aaa', + method: ENUM_REQUEST_METHOD.GET, + tags: [], + }; + const loggerComplete: ILogger = { + action: ENUM_LOGGER_ACTION.TEST, + description: 'test aaa', + user: `${new Types.ObjectId()}`, + apiKey: `${new Types.ObjectId()}`, + requestId: v4(), + role: { + _id: `${new Types.ObjectId()}`, + isAdmin: true, + }, + method: ENUM_REQUEST_METHOD.GET, + tags: [], + }; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + loggerService = moduleRef.get(LoggerService); + }); + + it('should be defined', () => { + expect(loggerService).toBeDefined(); + }); + + describe('info', () => { + it('should be called', async () => { + const test = jest.spyOn(loggerService, 'info'); + + loggerService.info(logger); + expect(test).toHaveBeenCalledWith(logger); + }); + + it('should be success', async () => { + const result = loggerService.info(logger); + jest.spyOn(loggerService, 'info').mockImplementation(() => result); + + expect(loggerService.info(logger)).toBe(result); + }); + + it('should be success complete', async () => { + const result = loggerService.info(loggerComplete); + jest.spyOn(loggerService, 'info').mockImplementation(() => result); + + expect(loggerService.info(loggerComplete)).toBe(result); + }); + }); + + describe('debug', () => { + it('should be called', async () => { + const test = jest.spyOn(loggerService, 'debug'); + + loggerService.debug(logger); + expect(test).toHaveBeenCalledWith(logger); + }); + + it('should be success', async () => { + const result = loggerService.debug(logger); + jest.spyOn(loggerService, 'debug').mockImplementation(() => result); + + expect(loggerService.debug(logger)).toBe(result); + }); + + it('should be success complete', async () => { + const result = loggerService.debug(loggerComplete); + jest.spyOn(loggerService, 'debug').mockImplementation(() => result); + + expect(loggerService.debug(loggerComplete)).toBe(result); + }); + }); + + describe('warning', () => { + it('should be called', async () => { + const test = jest.spyOn(loggerService, 'warning'); + + loggerService.warning(logger); + expect(test).toHaveBeenCalledWith(logger); + }); + + it('should be success', async () => { + const result = loggerService.warning(logger); + jest.spyOn(loggerService, 'warning').mockImplementation( + () => result + ); + + expect(loggerService.warning(logger)).toBe(result); + }); + + it('should be success complete', async () => { + const result = loggerService.warning(loggerComplete); + jest.spyOn(loggerService, 'warning').mockImplementation( + () => result + ); + + expect(loggerService.warning(loggerComplete)).toBe(result); + }); + }); + + describe('fatal', () => { + it('should be called', async () => { + const test = jest.spyOn(loggerService, 'fatal'); + + loggerService.fatal(logger); + expect(test).toHaveBeenCalledWith(logger); + }); + + it('should be success', async () => { + const result = loggerService.fatal(logger); + jest.spyOn(loggerService, 'fatal').mockImplementation(() => result); + + expect(loggerService.fatal(logger)).toBe(result); + }); + + it('should be success complete', async () => { + const result = loggerService.fatal(loggerComplete); + jest.spyOn(loggerService, 'fatal').mockImplementation(() => result); + + expect(loggerService.fatal(loggerComplete)).toBe(result); + }); + }); +}); diff --git a/packages/auth-api/test/message/messaga.service.spec.ts b/packages/auth-api/test/message/messaga.service.spec.ts new file mode 100644 index 0000000..a601a98 --- /dev/null +++ b/packages/auth-api/test/message/messaga.service.spec.ts @@ -0,0 +1,274 @@ +import { Test } from '@nestjs/testing'; +import { ValidationError } from 'class-validator'; +import { CoreModule } from 'src/core/core.module'; +import { MessageService } from 'src/message/service/message.service'; + +describe('MessageService', () => { + let messageService: MessageService; + + let validationError: ValidationError[]; + let validationErrorTwo: ValidationError[]; + let validationErrorThree: ValidationError[]; + let validationErrorConstrainEmpty: ValidationError[]; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + messageService = moduleRef.get(MessageService); + + validationError = [ + { + target: { + email: 'admin-mail.com', + password: 'aaAA@@123444', + rememberMe: true, + }, + value: 'admin-mail.com', + property: 'email', + children: [], + constraints: { isEmail: 'email must be an email' }, + }, + ]; + + validationErrorTwo = [ + { + target: { + email: 'admin-mail.com', + password: 'aaAA@@123444', + rememberMe: true, + }, + value: 'admin-mail.com', + property: 'email', + constraints: { isEmail: 'email must be an email' }, + children: [ + { + target: { + email: 'admin-mail.com', + password: 'aaAA@@123444', + rememberMe: true, + }, + value: 'admin-mail.com', + property: 'email', + constraints: { + isEmail: 'email must be an email', + }, + children: [], + }, + ], + }, + ]; + + validationErrorThree = [ + { + target: { + email: 'admin-mail.com', + password: 'aaAA@@123444', + rememberMe: true, + }, + value: 'admin-mail.com', + property: 'email', + constraints: { isEmail: 'email must be an email' }, + children: [ + { + target: { + email: 'admin-mail.com', + password: 'aaAA@@123444', + rememberMe: true, + }, + value: 'admin-mail.com', + property: 'email', + constraints: { + isEmail: 'email must be an email', + }, + children: [ + { + target: { + email: 'admin-mail.com', + password: 'aaAA@@123444', + rememberMe: true, + }, + value: 'admin-mail.com', + property: 'email', + constraints: { + isEmail: 'email must be an email', + }, + children: [], + }, + ], + }, + ], + }, + ]; + + validationErrorConstrainEmpty = [ + { + target: { + email: 'admin-mail.com', + password: 'aaAA@@123444', + rememberMe: true, + }, + value: 'admin-mail.com', + property: 'email', + children: [], + }, + ]; + }); + + it('should be defined', () => { + expect(messageService).toBeDefined(); + }); + + describe('get', () => { + it('should be called', async () => { + const test = jest.spyOn(messageService, 'get'); + + await messageService.get('test.hello'); + expect(test).toHaveBeenCalledWith('test.hello'); + }); + + it('should be success', async () => { + const message = messageService.get('test.hello'); + jest.spyOn(messageService, 'get').mockImplementation(() => message); + + expect(messageService.get('test.hello')).toBe(message); + }); + }); + + describe('getRequestErrorsMessage', () => { + it('should be called', async () => { + const test = jest.spyOn(messageService, 'getRequestErrorsMessage'); + + await messageService.getRequestErrorsMessage(validationError); + expect(test).toHaveBeenCalledWith(validationError); + }); + + it('single message should be success', async () => { + const message = await messageService.getRequestErrorsMessage( + validationError, + ['en'] + ); + jest.spyOn( + messageService, + 'getRequestErrorsMessage' + ).mockImplementation(async () => message); + + expect( + await messageService.getRequestErrorsMessage(validationError, [ + 'en', + ]) + ).toBe(message); + }); + + it('multi message should be success', async () => { + const message = await messageService.getRequestErrorsMessage( + validationError, + ['en', 'id'] + ); + jest.spyOn( + messageService, + 'getRequestErrorsMessage' + ).mockImplementation(async () => message); + + expect( + await messageService.getRequestErrorsMessage(validationError, [ + 'en', + 'id', + ]) + ).toBe(message); + }); + + it('should be success', async () => { + const message = await messageService.getRequestErrorsMessage( + validationError + ); + jest.spyOn( + messageService, + 'getRequestErrorsMessage' + ).mockImplementation(async () => message); + + expect( + await messageService.getRequestErrorsMessage(validationError) + ).toBe(message); + }); + + it('should be success', async () => { + const message = await messageService.getRequestErrorsMessage( + validationError + ); + jest.spyOn( + messageService, + 'getRequestErrorsMessage' + ).mockImplementation(async () => message); + + expect( + await messageService.getRequestErrorsMessage(validationError) + ).toBe(message); + }); + + it('two children should be success', async () => { + const message = await messageService.getRequestErrorsMessage( + validationErrorTwo + ); + jest.spyOn( + messageService, + 'getRequestErrorsMessage' + ).mockImplementation(async () => message); + + expect( + await messageService.getRequestErrorsMessage(validationErrorTwo) + ).toBe(message); + }); + + it('three children should be success', async () => { + const message = await messageService.getRequestErrorsMessage( + validationErrorThree + ); + jest.spyOn( + messageService, + 'getRequestErrorsMessage' + ).mockImplementation(async () => message); + + expect( + await messageService.getRequestErrorsMessage( + validationErrorThree + ) + ).toBe(message); + }); + + it('empty constrain should be success', async () => { + const message = await messageService.getRequestErrorsMessage( + validationErrorConstrainEmpty + ); + jest.spyOn( + messageService, + 'getRequestErrorsMessage' + ).mockImplementation(async () => message); + + expect( + await messageService.getRequestErrorsMessage( + validationErrorConstrainEmpty + ) + ).toBe(message); + }); + }); + + describe('getLanguages', () => { + it('should be called', async () => { + const test = jest.spyOn(messageService, 'getLanguages'); + + await messageService.getLanguages(); + expect(test).toHaveBeenCalled(); + }); + + it('should be success', async () => { + const languages = messageService.getLanguages(); + jest.spyOn(messageService, 'getLanguages').mockImplementation( + () => languages + ); + + expect(messageService.getLanguages()).toBe(languages); + }); + }); +}); diff --git a/packages/auth-api/test/pagination/pagination.service.spec.ts b/packages/auth-api/test/pagination/pagination.service.spec.ts new file mode 100644 index 0000000..72e09ca --- /dev/null +++ b/packages/auth-api/test/pagination/pagination.service.spec.ts @@ -0,0 +1,73 @@ +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { PaginationService } from 'src/pagination/service/pagination.service'; + +describe('PaginationService', () => { + let paginationService: PaginationService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + paginationService = moduleRef.get(PaginationService); + }); + + it('should be defined', () => { + expect(paginationService).toBeDefined(); + }); + + describe('skip', () => { + it('should be called', async () => { + const test = jest.spyOn(paginationService, 'skip'); + + await paginationService.skip(1, 10); + expect(test).toHaveBeenCalledWith(1, 10); + }); + + it('should be success', async () => { + const skip = paginationService.skip(1, 10); + jest.spyOn(paginationService, 'skip').mockImplementation( + () => skip + ); + + expect(paginationService.skip(1, 10)).toBe(skip); + }); + + it('max page should be success', async () => { + const skip = paginationService.skip(1, 150); + jest.spyOn(paginationService, 'skip').mockImplementation( + () => skip + ); + + expect(paginationService.skip(1, 150)).toBe(skip); + }); + + it('max per page should be success', async () => { + const skip = paginationService.skip(50, 10); + jest.spyOn(paginationService, 'skip').mockImplementation( + () => skip + ); + + expect(paginationService.skip(50, 10)).toBe(skip); + }); + }); + + describe('totalPage', () => { + it('should be called', async () => { + const test = jest.spyOn(paginationService, 'totalPage'); + + await paginationService.totalPage(100, 10); + expect(test).toHaveBeenCalledWith(100, 10); + }); + + it('should be success', async () => { + const totalPage = paginationService.totalPage(100, 10); + jest.spyOn(paginationService, 'totalPage').mockImplementation( + () => totalPage + ); + + expect(paginationService.totalPage(100, 10)).toBe(totalPage); + }); + }); +}); diff --git a/packages/auth-api/test/setting/setting.bulk.service.spec.ts b/packages/auth-api/test/setting/setting.bulk.service.spec.ts new file mode 100644 index 0000000..8619d34 --- /dev/null +++ b/packages/auth-api/test/setting/setting.bulk.service.spec.ts @@ -0,0 +1,42 @@ +import { Test } from '@nestjs/testing'; +import { CoreModule } from 'src/core/core.module'; +import { SettingBulkService } from 'src/setting/service/setting.bulk.service'; + +describe('SettingBulkService', () => { + let settingBulkService: SettingBulkService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + settingBulkService = + moduleRef.get(SettingBulkService); + }); + + it('should be defined', () => { + expect(settingBulkService).toBeDefined(); + }); + + describe('deleteMany', () => { + it('should be called', async () => { + const test = jest.spyOn(settingBulkService, 'deleteMany'); + + await settingBulkService.deleteMany({ name: 'test' }); + expect(test).toHaveBeenCalledWith({ name: 'test' }); + }); + + it('should be success', async () => { + const result = await settingBulkService.deleteMany({ + name: 'test', + }); + jest.spyOn(settingBulkService, 'deleteMany').mockImplementation( + async () => result + ); + + expect(await settingBulkService.deleteMany({ name: 'test' })).toBe( + result + ); + }); + }); +}); diff --git a/packages/auth-api/test/setting/setting.service.spec.ts b/packages/auth-api/test/setting/setting.service.spec.ts new file mode 100644 index 0000000..cd137b1 --- /dev/null +++ b/packages/auth-api/test/setting/setting.service.spec.ts @@ -0,0 +1,360 @@ +import { faker } from '@faker-js/faker'; +import { Test } from '@nestjs/testing'; +import { Types } from 'mongoose'; +import { CoreModule } from 'src/core/core.module'; +import { SettingService } from 'src/setting/service/setting.service'; + +describe('SettingService', () => { + let settingService: SettingService; + const _id = new Types.ObjectId(); + const _idString = `${_id}`; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CoreModule], + }).compile(); + + settingService = moduleRef.get(SettingService); + }); + + it('should be defined', () => { + expect(settingService).toBeDefined(); + }); + + describe('findAll', () => { + it('should be called', async () => { + const test = jest.spyOn(settingService, 'findAll'); + + await settingService.findAll(); + expect(test).toHaveBeenCalledWith(); + }); + + it('should be success', async () => { + const result = await settingService.findAll({}); + jest.spyOn(settingService, 'findAll').mockImplementation( + async () => result + ); + + expect(await settingService.findAll({})).toBe(result); + }); + + it('should be success with options limit and skip', async () => { + const result = await settingService.findAll( + {}, + { limit: 1, skip: 0 } + ); + jest.spyOn(settingService, 'findAll').mockImplementation( + async () => result + ); + + expect( + await settingService.findAll({}, { limit: 1, skip: 0 }) + ).toBe(result); + }); + + it('should be success with options limit, skip, sort', async () => { + const result = await settingService.findAll( + {}, + { limit: 1, skip: 0, sort: { name: 1 } } + ); + jest.spyOn(settingService, 'findAll').mockImplementation( + async () => result + ); + + expect( + await settingService.findAll( + {}, + { limit: 1, skip: 0, sort: { name: 1 } } + ) + ).toBe(result); + }); + }); + + describe('getTotal', () => { + it('should be called', async () => { + const test = jest.spyOn(settingService, 'getTotal'); + + await settingService.getTotal(); + expect(test).toHaveBeenCalledWith(); + }); + + it('should be success', async () => { + const result = await settingService.getTotal({}); + jest.spyOn(settingService, 'getTotal').mockImplementation( + async () => result + ); + + expect(await settingService.getTotal({})).toBe(result); + }); + }); + + describe('findOneById', () => { + it('should be called', async () => { + const test = jest.spyOn(settingService, 'findOneById'); + + await settingService.findOneById(_idString); + expect(test).toHaveBeenCalledWith(_idString); + }); + + it('should be success', async () => { + const result = await settingService.findOneById(_idString); + jest.spyOn(settingService, 'findOneById').mockImplementation( + async () => result + ); + + expect(await settingService.findOneById(_idString)).toBe(result); + }); + }); + + describe('findOneByName', () => { + it('should be called', async () => { + const test = jest.spyOn(settingService, 'findOneByName'); + + await settingService.findOneByName(_idString); + expect(test).toHaveBeenCalledWith(_idString); + }); + + it('should be success', async () => { + const result = await settingService.findOneByName(_idString); + jest.spyOn(settingService, 'findOneByName').mockImplementation( + async () => result + ); + + expect(await settingService.findOneByName(_idString)).toBe(result); + }); + }); + + describe('create', () => { + it('should be called', async () => { + const test = jest.spyOn(settingService, 'create'); + + const setting = await settingService.create({ + name: faker.name.firstName(), + description: 'test', + value: 1, + }); + expect(test).toHaveBeenCalledWith({ + name: setting.name, + description: setting.description, + value: setting.value, + }); + + await settingService.deleteOne({ _id: setting._id }); + }); + + it('should be success', async () => { + const result = await settingService.create({ + name: faker.name.firstName(), + description: 'test', + value: 1, + }); + + jest.spyOn(settingService, 'create').mockImplementation( + async () => result + ); + + expect( + await settingService.create({ + name: result.name, + description: result.description, + value: result.value, + }) + ).toBe(result); + + await settingService.deleteOne({ _id: result._id }); + }); + + it('should be success string', async () => { + const result = await settingService.create({ + name: faker.name.firstName(), + description: 'test', + value: '1', + }); + jest.spyOn(settingService, 'create').mockImplementation( + async () => result + ); + + expect( + await settingService.create({ + name: result.name, + description: 'test', + value: '1', + }) + ).toBe(result); + + await settingService.deleteOne({ _id: result._id }); + }); + }); + + describe('updateOneById', () => { + it('should be called', async () => { + const test = jest.spyOn(settingService, 'updateOneById'); + + const setting = await settingService.create({ + name: faker.name.firstName(), + description: 'test', + value: 1, + }); + await settingService.updateOneById(setting._id, { value: 2 }); + expect(test).toHaveBeenCalledWith(setting._id, { value: 2 }); + + await settingService.deleteOne({ _id: setting._id }); + }); + + it('should be success', async () => { + const setting = await settingService.create({ + name: faker.name.firstName(), + description: 'test', + value: 1, + }); + + const result = await settingService.updateOneById(setting._id, { + value: 1, + }); + jest.spyOn(settingService, 'updateOneById').mockImplementation( + async () => result + ); + + expect( + await settingService.updateOneById(setting._id, { + value: 1, + }) + ).toBe(result); + + await settingService.deleteOne({ _id: setting._id }); + }); + + it('should be success string', async () => { + const setting = await settingService.create({ + name: faker.name.firstName(), + description: 'test', + value: 1, + }); + + const result = await settingService.updateOneById(setting._id, { + value: '2', + }); + jest.spyOn(settingService, 'updateOneById').mockImplementation( + async () => result + ); + + expect( + await settingService.updateOneById(setting._id, { + value: '2', + }) + ).toBe(result); + + await settingService.deleteOne({ _id: setting._id }); + }); + }); + + describe('serializationList', () => { + it('should be called', async () => { + const test = jest.spyOn(settingService, 'serializationList'); + + const settings = await settingService.findAll( + {}, + { limit: 1, skip: 0 } + ); + await settingService.serializationList(settings); + expect(test).toHaveBeenCalledWith(settings); + }); + + it('should be success', async () => { + const settings = await settingService.findAll( + {}, + { limit: 1, skip: 0 } + ); + const result = await settingService.serializationList(settings); + jest.spyOn(settingService, 'serializationList').mockImplementation( + async () => result + ); + + expect(await settingService.serializationList(settings)).toBe( + result + ); + }); + }); + + describe('serializationGet', () => { + it('should be called', async () => { + const test = jest.spyOn(settingService, 'serializationGet'); + + const setting = await settingService.create({ + name: faker.name.firstName(), + description: 'test', + value: 1, + }); + await settingService.serializationGet(setting); + expect(test).toHaveBeenCalledWith(setting); + + await settingService.deleteOne({ _id: setting._id }); + }); + + it('should be success', async () => { + const setting = await settingService.create({ + name: faker.name.firstName(), + description: 'test', + value: 1, + }); + const result = await settingService.serializationGet(setting); + jest.spyOn(settingService, 'serializationGet').mockImplementation( + async () => result + ); + + expect(await settingService.serializationGet(setting)).toBe(result); + + await settingService.deleteOne({ _id: setting._id }); + }); + }); + + describe('deleteOne', () => { + it('should be called', async () => { + const test = jest.spyOn(settingService, 'deleteOne'); + + const setting = await settingService.create({ + name: faker.name.firstName(), + description: 'test', + value: 1, + }); + await settingService.deleteOne({ _id: setting._id }); + expect(test).toHaveBeenCalledWith({ _id: setting._id }); + }); + + it('should be success', async () => { + const setting = await settingService.create({ + name: faker.name.firstName(), + description: 'test', + value: 1, + }); + const result = await settingService.deleteOne({ _id: setting._id }); + jest.spyOn(settingService, 'deleteOne').mockImplementation( + async () => result + ); + + expect(await settingService.deleteOne({ _id: setting._id })).toBe( + result + ); + + await settingService.deleteOne({ _id: setting._id }); + }); + }); + + describe('convertValue', () => { + it('should be called', async () => { + const test = jest.spyOn(settingService, 'convertValue'); + + await settingService.convertValue('1'); + expect(test).toHaveBeenCalledWith('1'); + }); + + it('should be success', async () => { + const result = await settingService.convertValue('1'); + jest.spyOn(settingService, 'convertValue').mockImplementation( + async () => result + ); + + expect(await settingService.convertValue('1')).toBe(result); + }); + }); +}); diff --git a/packages/auth-api/tsconfig.build.json b/packages/auth-api/tsconfig.build.json new file mode 100644 index 0000000..f08d909 --- /dev/null +++ b/packages/auth-api/tsconfig.build.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "exclude": [ + "node_modules", + "dist", + "test", + "e2e", + "prod", + "integration" + ], + "include": [ + "src" + ], +} diff --git a/packages/auth-api/tsconfig.json b/packages/auth-api/tsconfig.json new file mode 100644 index 0000000..91102a3 --- /dev/null +++ b/packages/auth-api/tsconfig.json @@ -0,0 +1,54 @@ +{ + "extends": "../../tsconfig.json", + + "compilerOptions": { + "rootDirs": [ + "src", + "test", + "e2e", + "integration" + ], + // "assets": [ + // "resources/**/*.json" + // ], + "outDir": "./dist", + "baseUrl": "./", + + //"declaration": true, + //"declarationDir": "./dist/types" + + // "module": "commonjs", + // "moduleResolution": "node", + // "target": "ESNext", + // "allowJs": false, + // "allowSyntheticDefaultImports": true, + // "declaration": false, + // "declarationMap": false, + // "esModuleInterop": true, + // "emitDecoratorMetadata": true, + // "experimentalDecorators": true, + // "incremental": true, + // "newLine": "LF", + // "noEmitOnError": true, + // "noFallthroughCasesInSwitch": true, + // "noImplicitReturns": true, + // "noUnusedLocals": false, + // "noUnusedParameters": false, + // "removeComments": true, + // "resolveJsonModule": true, + // "skipLibCheck": true, + // "sourceMap": true, + // "useDefineForClassFields": false, + }, + "include": [ + "src", + "test", + "e2e", + "integration" + ], + "exclude": [ + "node_modules", + "dist", + "prod" + ] +} diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..7b6dca1 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +cd packages +for D in *; do + if [ -d "${D}" ]; then + cd ${D} + cp ../../.npmignore ./.npmignore + cd ../ + fi +done + +cd ../ + +tsc; \ No newline at end of file diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100755 index 0000000..6988587 --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1,11 @@ +rm -r node_modules; +rm package-lock.json; +rm yarn.lock; + +lerna clean --yes; +find . -name "package-lock.json" -delete +find . -name "yarn.lock" -delete + +#npm install +yarn +lerna bootstrap \ No newline at end of file diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..a842448 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,39 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "module": "commonjs", + "moduleResolution": "node", + "target": "ESNext", + "allowJs": false, + "allowSyntheticDefaultImports": true, + "declaration": true, + "declarationMap": false, + "esModuleInterop": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "incremental": true, + "newLine": "LF", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "outDir": "./dist", + "removeComments": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "useDefineForClassFields": false, + "typeRoots": [ + "node_modules/@types", + "types" + ] + }, + "exclude": [ + "node_modules", + "dist", + "tmp", + "prod", + "**/*.spec.ts" + ] +} \ No newline at end of file diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..22a8f4b --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "exclude": [ + "node_modules", + "test", + "dist", + "**/*spec.ts" + ] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b84c26a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.base.json" +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..0191c1f --- /dev/null +++ b/yarn.lock @@ -0,0 +1,11288 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@angular-devkit/core@13.3.5": + version "13.3.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-13.3.5.tgz#c5f32f4f99b5cad8df9cf3cf4da9c4b1335c1155" + integrity sha512-w7vzK4VoYP9rLgxJ2SwEfrkpKybdD+QgQZlsDBzT0C6Ebp7b4gkNcNVFo8EiZvfDl6Yplw2IAP7g7fs3STn0hQ== + dependencies: + ajv "8.9.0" + ajv-formats "2.1.1" + fast-json-stable-stringify "2.1.0" + magic-string "0.25.7" + rxjs "6.6.7" + source-map "0.7.3" + +"@angular-devkit/schematics-cli@13.3.5": + version "13.3.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics-cli/-/schematics-cli-13.3.5.tgz#e9b6199189d6e5d8e08bb53e2c9cac45314513c8" + integrity sha512-ARX20ebtfwzef8GdXIcB6uv0sjTsaEniZyXBFchEKD6kR5EYZVaBL+ZVUbmsU1d0XY///WzW7pqwCyu5H1u+vw== + dependencies: + "@angular-devkit/core" "13.3.5" + "@angular-devkit/schematics" "13.3.5" + ansi-colors "4.1.1" + inquirer "8.2.0" + minimist "1.2.6" + symbol-observable "4.0.0" + +"@angular-devkit/schematics@13.3.5": + version "13.3.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-13.3.5.tgz#9cb03ac99ee14173a6fa00fd7ca94fa42600c163" + integrity sha512-0N/kL/Vfx0yVAEwa3HYxNx9wYb+G9r1JrLjJQQzDp+z9LtcojNf7j3oey6NXrDUs1WjVZOa/AIdRl3/DuaoG5w== + dependencies: + "@angular-devkit/core" "13.3.5" + jsonc-parser "3.0.0" + magic-string "0.25.7" + ora "5.4.1" + rxjs "6.6.7" + +"@aws-crypto/crc32@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-2.0.0.tgz#4ad432a3c03ec3087c5540ff6e41e6565d2dc153" + integrity sha512-TvE1r2CUueyXOuHdEigYjIZVesInd9KN+K/TFFNfkkxRThiNxO6i4ZqqAVMoEjAamZZ1AA8WXJkjCz7YShHPQA== + dependencies: + "@aws-crypto/util" "^2.0.0" + "@aws-sdk/types" "^3.1.0" + tslib "^1.11.1" + +"@aws-crypto/crc32c@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/crc32c/-/crc32c-2.0.0.tgz#4235336ef78f169f6a05248906703b9b78da676e" + integrity sha512-vF0eMdMHx3O3MoOXUfBZry8Y4ZDtcuskjjKgJz8YfIDjLStxTZrYXk+kZqtl6A0uCmmiN/Eb/JbC/CndTV1MHg== + dependencies: + "@aws-crypto/util" "^2.0.0" + "@aws-sdk/types" "^3.1.0" + tslib "^1.11.1" + +"@aws-crypto/ie11-detection@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/ie11-detection/-/ie11-detection-2.0.0.tgz#bb6c2facf8f03457e949dcf0921477397ffa4c6e" + integrity sha512-pkVXf/dq6PITJ0jzYZ69VhL8VFOFoPZLZqtU/12SGnzYuJOOGNfF41q9GxdI1yqC8R13Rq3jOLKDFpUJFT5eTA== + dependencies: + tslib "^1.11.1" + +"@aws-crypto/sha1-browser@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha1-browser/-/sha1-browser-2.0.0.tgz#71e735df20ea1d38f59259c4b1a2e00ca74a0eea" + integrity sha512-3fIVRjPFY8EG5HWXR+ZJZMdWNRpwbxGzJ9IH9q93FpbgCH8u8GHRi46mZXp3cYD7gealmyqpm3ThZwLKJjWJhA== + dependencies: + "@aws-crypto/ie11-detection" "^2.0.0" + "@aws-crypto/supports-web-crypto" "^2.0.0" + "@aws-sdk/types" "^3.1.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@aws-sdk/util-utf8-browser" "^3.0.0" + tslib "^1.11.1" + +"@aws-crypto/sha256-browser@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz#741c9024df55ec59b51e5b1f5d806a4852699fb5" + integrity sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A== + dependencies: + "@aws-crypto/ie11-detection" "^2.0.0" + "@aws-crypto/sha256-js" "^2.0.0" + "@aws-crypto/supports-web-crypto" "^2.0.0" + "@aws-crypto/util" "^2.0.0" + "@aws-sdk/types" "^3.1.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@aws-sdk/util-utf8-browser" "^3.0.0" + tslib "^1.11.1" + +"@aws-crypto/sha256-js@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz#f1f936039bdebd0b9e2dd834d65afdc2aac4efcb" + integrity sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig== + dependencies: + "@aws-crypto/util" "^2.0.0" + "@aws-sdk/types" "^3.1.0" + tslib "^1.11.1" + +"@aws-crypto/sha256-js@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-2.0.1.tgz#79e1e6cf61f652ef2089c08d471c722ecf1626a9" + integrity sha512-mbHTBSPBvg6o/mN/c18Z/zifM05eJrapj5ggoOIeHIWckvkv5VgGi7r/wYpt+QAO2ySKXLNvH2d8L7bne4xrMQ== + dependencies: + "@aws-crypto/util" "^2.0.1" + "@aws-sdk/types" "^3.1.0" + tslib "^1.11.1" + +"@aws-crypto/supports-web-crypto@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.0.tgz#fd6cde30b88f77d5a4f57b2c37c560d918014f9e" + integrity sha512-Ge7WQ3E0OC7FHYprsZV3h0QIcpdyJLvIeg+uTuHqRYm8D6qCFJoiC+edSzSyFiHtZf+NOQDJ1q46qxjtzIY2nA== + dependencies: + tslib "^1.11.1" + +"@aws-crypto/util@^2.0.0", "@aws-crypto/util@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-2.0.1.tgz#976cf619cf85084ca85ec5eb947a6ac6b8b5c98c" + integrity sha512-JJmFFwvbm08lULw4Nm5QOLg8+lAQeC8aCXK5xrtxntYzYXCGfHwUJ4Is3770Q7HmICsXthGQ+ZsDL7C2uH3yBQ== + dependencies: + "@aws-sdk/types" "^3.1.0" + "@aws-sdk/util-utf8-browser" "^3.0.0" + tslib "^1.11.1" + +"@aws-sdk/abort-controller@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/abort-controller/-/abort-controller-3.110.0.tgz#15b493b776ec4f7236c6ad6134a6fe87e9dc5292" + integrity sha512-zok/WEVuK7Jh6V9YeA56pNZtxUASon9LTkS7vE65A4UFmNkPGNBCNgoiBcbhWfxwrZ8wtXcQk6rtUut39831mA== + dependencies: + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/chunked-blob-reader-native@3.109.0": + version "3.109.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/chunked-blob-reader-native/-/chunked-blob-reader-native-3.109.0.tgz#4db2ec81faf38fe33cf9dd6f75641afe0826dcfd" + integrity sha512-Ybn3vDZ3CqGyprL2qdF6QZqoqlx8lA3qOJepobjuKKDRw+KgGxjUY4NvWe0R2MdRoduyaDj6uvhIay0S1MOSJQ== + dependencies: + "@aws-sdk/util-base64-browser" "3.109.0" + tslib "^2.3.1" + +"@aws-sdk/chunked-blob-reader@3.55.0": + version "3.55.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/chunked-blob-reader/-/chunked-blob-reader-3.55.0.tgz#db240c78e7c4c826e707f0ca32a4d221c41cf6a0" + integrity sha512-o/xjMCq81opAjSBjt7YdHJwIJcGVG5XIV9+C2KXcY5QwVimkOKPybWTv0mXPvSwSilSx+EhpLNhkcJuXdzhw4w== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/client-s3@^3.113.0": + version "3.113.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.113.0.tgz#fc416139a1d1f5a8e07ac6f3d453042c6d84c4f9" + integrity sha512-QHynLFWwhQFB2bULxMOlnIYzKPmE6ky5yRo0NPGklz4bnWc8RY/vSvlaii4JBxPee9TGxNM1/NCF0oMLUdXK3Q== + dependencies: + "@aws-crypto/sha1-browser" "2.0.0" + "@aws-crypto/sha256-browser" "2.0.0" + "@aws-crypto/sha256-js" "2.0.0" + "@aws-sdk/client-sts" "3.112.0" + "@aws-sdk/config-resolver" "3.110.0" + "@aws-sdk/credential-provider-node" "3.112.0" + "@aws-sdk/eventstream-serde-browser" "3.110.0" + "@aws-sdk/eventstream-serde-config-resolver" "3.110.0" + "@aws-sdk/eventstream-serde-node" "3.110.0" + "@aws-sdk/fetch-http-handler" "3.110.0" + "@aws-sdk/hash-blob-browser" "3.110.0" + "@aws-sdk/hash-node" "3.110.0" + "@aws-sdk/hash-stream-node" "3.110.0" + "@aws-sdk/invalid-dependency" "3.110.0" + "@aws-sdk/md5-js" "3.110.0" + "@aws-sdk/middleware-bucket-endpoint" "3.110.0" + "@aws-sdk/middleware-content-length" "3.110.0" + "@aws-sdk/middleware-expect-continue" "3.113.0" + "@aws-sdk/middleware-flexible-checksums" "3.110.0" + "@aws-sdk/middleware-host-header" "3.110.0" + "@aws-sdk/middleware-location-constraint" "3.110.0" + "@aws-sdk/middleware-logger" "3.110.0" + "@aws-sdk/middleware-recursion-detection" "3.110.0" + "@aws-sdk/middleware-retry" "3.110.0" + "@aws-sdk/middleware-sdk-s3" "3.110.0" + "@aws-sdk/middleware-serde" "3.110.0" + "@aws-sdk/middleware-signing" "3.110.0" + "@aws-sdk/middleware-ssec" "3.110.0" + "@aws-sdk/middleware-stack" "3.110.0" + "@aws-sdk/middleware-user-agent" "3.110.0" + "@aws-sdk/node-config-provider" "3.110.0" + "@aws-sdk/node-http-handler" "3.110.0" + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/signature-v4-multi-region" "3.110.0" + "@aws-sdk/smithy-client" "3.110.0" + "@aws-sdk/types" "3.110.0" + "@aws-sdk/url-parser" "3.110.0" + "@aws-sdk/util-base64-browser" "3.109.0" + "@aws-sdk/util-base64-node" "3.55.0" + "@aws-sdk/util-body-length-browser" "3.55.0" + "@aws-sdk/util-body-length-node" "3.55.0" + "@aws-sdk/util-defaults-mode-browser" "3.110.0" + "@aws-sdk/util-defaults-mode-node" "3.110.0" + "@aws-sdk/util-stream-browser" "3.110.0" + "@aws-sdk/util-stream-node" "3.110.0" + "@aws-sdk/util-user-agent-browser" "3.110.0" + "@aws-sdk/util-user-agent-node" "3.110.0" + "@aws-sdk/util-utf8-browser" "3.109.0" + "@aws-sdk/util-utf8-node" "3.109.0" + "@aws-sdk/util-waiter" "3.110.0" + "@aws-sdk/xml-builder" "3.109.0" + entities "2.2.0" + fast-xml-parser "3.19.0" + tslib "^2.3.1" + +"@aws-sdk/client-sso@3.112.0": + version "3.112.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.112.0.tgz#5f308d34566441b94c633018af74036530e69ac6" + integrity sha512-FwFmiapxuVQiyMdDaBvCpajnJkVWEUHBdO+7rIpzgKHkODEPou5/AwboaGRPEFYULOyYeI0HiDFzpK0G6de+7Q== + dependencies: + "@aws-crypto/sha256-browser" "2.0.0" + "@aws-crypto/sha256-js" "2.0.0" + "@aws-sdk/config-resolver" "3.110.0" + "@aws-sdk/fetch-http-handler" "3.110.0" + "@aws-sdk/hash-node" "3.110.0" + "@aws-sdk/invalid-dependency" "3.110.0" + "@aws-sdk/middleware-content-length" "3.110.0" + "@aws-sdk/middleware-host-header" "3.110.0" + "@aws-sdk/middleware-logger" "3.110.0" + "@aws-sdk/middleware-recursion-detection" "3.110.0" + "@aws-sdk/middleware-retry" "3.110.0" + "@aws-sdk/middleware-serde" "3.110.0" + "@aws-sdk/middleware-stack" "3.110.0" + "@aws-sdk/middleware-user-agent" "3.110.0" + "@aws-sdk/node-config-provider" "3.110.0" + "@aws-sdk/node-http-handler" "3.110.0" + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/smithy-client" "3.110.0" + "@aws-sdk/types" "3.110.0" + "@aws-sdk/url-parser" "3.110.0" + "@aws-sdk/util-base64-browser" "3.109.0" + "@aws-sdk/util-base64-node" "3.55.0" + "@aws-sdk/util-body-length-browser" "3.55.0" + "@aws-sdk/util-body-length-node" "3.55.0" + "@aws-sdk/util-defaults-mode-browser" "3.110.0" + "@aws-sdk/util-defaults-mode-node" "3.110.0" + "@aws-sdk/util-user-agent-browser" "3.110.0" + "@aws-sdk/util-user-agent-node" "3.110.0" + "@aws-sdk/util-utf8-browser" "3.109.0" + "@aws-sdk/util-utf8-node" "3.109.0" + tslib "^2.3.1" + +"@aws-sdk/client-sts@3.112.0": + version "3.112.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.112.0.tgz#97aa584562b7b9b18ca09b5a025e3f5093e07a72" + integrity sha512-hSApRO2wg3jk9VRGM6SCZO3aFP7DKVSUqs6FrvlXlj+JU88ZKObjrGE61cCzXoD89Dh+b9t8A2T6W51Nzriaxw== + dependencies: + "@aws-crypto/sha256-browser" "2.0.0" + "@aws-crypto/sha256-js" "2.0.0" + "@aws-sdk/config-resolver" "3.110.0" + "@aws-sdk/credential-provider-node" "3.112.0" + "@aws-sdk/fetch-http-handler" "3.110.0" + "@aws-sdk/hash-node" "3.110.0" + "@aws-sdk/invalid-dependency" "3.110.0" + "@aws-sdk/middleware-content-length" "3.110.0" + "@aws-sdk/middleware-host-header" "3.110.0" + "@aws-sdk/middleware-logger" "3.110.0" + "@aws-sdk/middleware-recursion-detection" "3.110.0" + "@aws-sdk/middleware-retry" "3.110.0" + "@aws-sdk/middleware-sdk-sts" "3.110.0" + "@aws-sdk/middleware-serde" "3.110.0" + "@aws-sdk/middleware-signing" "3.110.0" + "@aws-sdk/middleware-stack" "3.110.0" + "@aws-sdk/middleware-user-agent" "3.110.0" + "@aws-sdk/node-config-provider" "3.110.0" + "@aws-sdk/node-http-handler" "3.110.0" + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/smithy-client" "3.110.0" + "@aws-sdk/types" "3.110.0" + "@aws-sdk/url-parser" "3.110.0" + "@aws-sdk/util-base64-browser" "3.109.0" + "@aws-sdk/util-base64-node" "3.55.0" + "@aws-sdk/util-body-length-browser" "3.55.0" + "@aws-sdk/util-body-length-node" "3.55.0" + "@aws-sdk/util-defaults-mode-browser" "3.110.0" + "@aws-sdk/util-defaults-mode-node" "3.110.0" + "@aws-sdk/util-user-agent-browser" "3.110.0" + "@aws-sdk/util-user-agent-node" "3.110.0" + "@aws-sdk/util-utf8-browser" "3.109.0" + "@aws-sdk/util-utf8-node" "3.109.0" + entities "2.2.0" + fast-xml-parser "3.19.0" + tslib "^2.3.1" + +"@aws-sdk/config-resolver@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/config-resolver/-/config-resolver-3.110.0.tgz#93de506934aa06dd973e5e3dab95b629697372f9" + integrity sha512-7VvtKy4CL63BAktQ2vgsjhWDSXpkXO5YdiI56LQnHztrvSuJBBaxJ7R1p/k0b2tEUhYKUziAIW8EKE/7EGPR4g== + dependencies: + "@aws-sdk/signature-v4" "3.110.0" + "@aws-sdk/types" "3.110.0" + "@aws-sdk/util-config-provider" "3.109.0" + "@aws-sdk/util-middleware" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-env@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.110.0.tgz#c95552fc0a3ae857ced0e171e53082cf3c84bc74" + integrity sha512-oFU3IYk/Bl5tdsz1qigtm3I25a9cvXPqlE8VjYjxVDdLujF5zd/4HLbhP4GQWhpEwZmM1ijcSNfLcyywVevTZg== + dependencies: + "@aws-sdk/property-provider" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-imds@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.110.0.tgz#ba4f178ccab65c5760bce38e7f694584dad3fd74" + integrity sha512-atl+7/dAB+8fG9XI2fYyCgXKYDbOzot65VAwis+14bOEUCVp7PCJifBEZ/L8GEq564p+Fa2p1IpV0wuQXxqFUQ== + dependencies: + "@aws-sdk/node-config-provider" "3.110.0" + "@aws-sdk/property-provider" "3.110.0" + "@aws-sdk/types" "3.110.0" + "@aws-sdk/url-parser" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-ini@3.112.0": + version "3.112.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.112.0.tgz#46001d9e61957ddc5a253ba2c45be92474960b43" + integrity sha512-ebgZ6/jZdTGHQ3zfq/ccmS+7YmLk6yUWHDmh69VK+B1Dd+S1jFwbD9EQ+pYWCp/gEl9F620NSwb6KghRylPWEQ== + dependencies: + "@aws-sdk/credential-provider-env" "3.110.0" + "@aws-sdk/credential-provider-imds" "3.110.0" + "@aws-sdk/credential-provider-sso" "3.112.0" + "@aws-sdk/credential-provider-web-identity" "3.110.0" + "@aws-sdk/property-provider" "3.110.0" + "@aws-sdk/shared-ini-file-loader" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-node@3.112.0": + version "3.112.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.112.0.tgz#66a87f8d759aabca06bf3205453e1a28301ddeac" + integrity sha512-7txS7P3BAaU4cksFw/PnoVskVvO8h/TPvOl/BxFtCiUdwA6FRltLvBeMlN08fwUoqgM6z06q8areBdeDqCHOSw== + dependencies: + "@aws-sdk/credential-provider-env" "3.110.0" + "@aws-sdk/credential-provider-imds" "3.110.0" + "@aws-sdk/credential-provider-ini" "3.112.0" + "@aws-sdk/credential-provider-process" "3.110.0" + "@aws-sdk/credential-provider-sso" "3.112.0" + "@aws-sdk/credential-provider-web-identity" "3.110.0" + "@aws-sdk/property-provider" "3.110.0" + "@aws-sdk/shared-ini-file-loader" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-process@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.110.0.tgz#1f4543edd532beb4b690e6f3aaf74d00af3be5c4" + integrity sha512-JJcZePvRTfQHYj/+EEY13yItnZH/e8exlARFUjN0L13UrgHpOJtDQBa+YBHXo6MbTFQh+re25z2kzc+zOYSMNQ== + dependencies: + "@aws-sdk/property-provider" "3.110.0" + "@aws-sdk/shared-ini-file-loader" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-sso@3.112.0": + version "3.112.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.112.0.tgz#1ef5e8f331040b8e5be9b011f8074c682bf068ba" + integrity sha512-b6rOrSXbNK3fGyPvNpyF5zdktmAoNOqHCTmFSUcxRxOipyRGb5JACsbjWthIQkpWkpNCT8GFNLEg9spXPFIdLA== + dependencies: + "@aws-sdk/client-sso" "3.112.0" + "@aws-sdk/property-provider" "3.110.0" + "@aws-sdk/shared-ini-file-loader" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-web-identity@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.110.0.tgz#236e192826c3856e1f2b8eaa1ad126affd641082" + integrity sha512-e4e5u7v3fsUFZsMcFMhMy1NdJBQpunYcLwpYlszm3OEICwTTekQ+hVvnVRd134doHvzepE4yp9sAop0Cj+IRVQ== + dependencies: + "@aws-sdk/property-provider" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/eventstream-marshaller@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-marshaller/-/eventstream-marshaller-3.110.0.tgz#89c6496a906079f1627fd460ab3c342dc2ea9130" + integrity sha512-ZVJI2iCmjxigtLKfc9v48NHY34Qos5l9wgxzB1lU+RwaBppbmjogvIpPlKewEuAFsLTrErUK4ONBWGGsvLYlBQ== + dependencies: + "@aws-crypto/crc32" "2.0.0" + "@aws-sdk/types" "3.110.0" + "@aws-sdk/util-hex-encoding" "3.109.0" + tslib "^2.3.1" + +"@aws-sdk/eventstream-serde-browser@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-browser/-/eventstream-serde-browser-3.110.0.tgz#7db96e147756606097dbfa99f7d1cd1b0077a963" + integrity sha512-zeZpKO9Ccsg6seB9oYf9rEQkYfM4nWnyQJtfGvpj/BlkJ7i3UhpbVca8q6aC61WLb3fcO/JROqNfDK1Vis8RgA== + dependencies: + "@aws-sdk/eventstream-marshaller" "3.110.0" + "@aws-sdk/eventstream-serde-universal" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/eventstream-serde-config-resolver@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.110.0.tgz#5ec8dee49a595b6079fc52bc4355bc15626bb9de" + integrity sha512-0kyKUU5/46OGe6rgIqbNRJEQhNYwxLdgcJXlBl6q6CdgyQApz6jsAgG0C5xhSLSi4iJijDRriJTowAhkq4AlWQ== + dependencies: + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/eventstream-serde-node@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-node/-/eventstream-serde-node-3.110.0.tgz#07ca20ec5684e0e389951bfa069f1000037d055b" + integrity sha512-Bd7d57BANdy1RBnZ6EBxEaDzC4DidR40EMEk08Ho3+md6CW/vmW63n9wAhKjdoq9a+Hp6aDWP4huVKhyT/d6PA== + dependencies: + "@aws-sdk/eventstream-marshaller" "3.110.0" + "@aws-sdk/eventstream-serde-universal" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/eventstream-serde-universal@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-universal/-/eventstream-serde-universal-3.110.0.tgz#f24fdec4dc7304c690d3f842b844b50a57110087" + integrity sha512-VjzOxDaHCzPlZs+9UqqQABP47gCWf97kqwhuoPUsCzV8leEHnLfAX3BvIZ58kNr4Fycua5AgK7Ww6uFfXVeW8w== + dependencies: + "@aws-sdk/eventstream-marshaller" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/fetch-http-handler@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.110.0.tgz#0b6d552659b779c49ba0f99c78a57755864bf1b0" + integrity sha512-vk+K4GeCZL2J2rtvKO+T0Q7i3MDpEGZBMg5K2tj9sMcEQwty0BF0aFnP7Eu2l4/Zif2z1mWuUFM2WcZI6DVnbw== + dependencies: + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/querystring-builder" "3.110.0" + "@aws-sdk/types" "3.110.0" + "@aws-sdk/util-base64-browser" "3.109.0" + tslib "^2.3.1" + +"@aws-sdk/hash-blob-browser@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/hash-blob-browser/-/hash-blob-browser-3.110.0.tgz#9237d9cd239ed1e964cf567dd4d2891b30984417" + integrity sha512-NkTosjlYwP2dcBXY6yzhNafAK+W2nceheffvWdyGA29+E9YdRjDminXvKc/WAkZUMOW0CaCbD90otOiimAAYyQ== + dependencies: + "@aws-sdk/chunked-blob-reader" "3.55.0" + "@aws-sdk/chunked-blob-reader-native" "3.109.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/hash-node@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/hash-node/-/hash-node-3.110.0.tgz#b225bfd16596b6485c1c610e8fef8de1e40931c4" + integrity sha512-wakl+kP2O8wTGYiQ3InZy+CVfGrIpFfq9fo4zif9PZac0BbUbguUU1dkY34uZiaf+4o2/9MoDYrHU2HYeXKxWw== + dependencies: + "@aws-sdk/types" "3.110.0" + "@aws-sdk/util-buffer-from" "3.55.0" + tslib "^2.3.1" + +"@aws-sdk/hash-stream-node@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/hash-stream-node/-/hash-stream-node-3.110.0.tgz#786304b29d27a8e3814a49fb93208e8231ebca87" + integrity sha512-srlStn+dCnBlQy4oWBz3oFS8vT5Xgxhra91rt9U+vHruCyQ0L1es0J87X4uwy2HRlnIw3daPtVLtxekahEXzKQ== + dependencies: + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/invalid-dependency@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/invalid-dependency/-/invalid-dependency-3.110.0.tgz#9104dfd40e35b6737dc7ab01f4e79c76c1109c44" + integrity sha512-O8J1InmtJkoiUMbQDtxBfOzgigBp9iSVsNXQrhs2qHh3826cJOfE7NGT3u+NMw73Pk5j2cfmOh1+7k/76IqxOg== + dependencies: + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/is-array-buffer@3.55.0": + version "3.55.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/is-array-buffer/-/is-array-buffer-3.55.0.tgz#c46122c5636f01d5895e5256a587768c3425ea7a" + integrity sha512-NbiPHVYuPxdqdFd6FxzzN3H1BQn/iWA3ri3Ry7AyLeP/tGs1yzEWMwf8BN8TSMALI0GXT6Sh0GDWy3Ok5xB6DA== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/md5-js@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/md5-js/-/md5-js-3.110.0.tgz#0a8745cbcaa609452d034e1b0edfa8f0cf45e2ae" + integrity sha512-66gV6CH8O7ymTZMIbGjdUI71K7ErDfudhtN/ULb97kD2TYX4NlFtxNZxx3+iZH1G0H636lWm9hJcU5ELG9B+bw== + dependencies: + "@aws-sdk/types" "3.110.0" + "@aws-sdk/util-utf8-browser" "3.109.0" + "@aws-sdk/util-utf8-node" "3.109.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-bucket-endpoint@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.110.0.tgz#76e0dce1d16750340da76736c5737d790db1a95a" + integrity sha512-l1q0KzMRFyGSSc7LZGEh2xhCha1933C8uJE5g23b7dZdklEU5I62l4daELo+TBANcxFzDiRXd6g5mly/T+ZTSg== + dependencies: + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/types" "3.110.0" + "@aws-sdk/util-arn-parser" "3.55.0" + "@aws-sdk/util-config-provider" "3.109.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-content-length@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-content-length/-/middleware-content-length-3.110.0.tgz#f4dc3508952c5fae9740f172d3b76135dd4dba37" + integrity sha512-hKU+zdqfAJQg22LXMVu/z35nNIHrVAKpVKPe9+WYVdL/Z7JKUPK7QymqKGOyDuDbzW6OxyulC1zKGEX12zGmdA== + dependencies: + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-expect-continue@3.113.0": + version "3.113.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.113.0.tgz#0129967f40ef57eec922ef8e126d77b90853a0fe" + integrity sha512-LLtSunCYVWeAhRP+6enn0kYF119WooV6gepMGOWeRCpKXO2iyi8YOx2Mtgc3T8ybiAG/dVlmZoX47Y1HINcuqg== + dependencies: + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-flexible-checksums@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.110.0.tgz#bbf6009d45b7080e262a7351a86acf083ee22af1" + integrity sha512-Z/v1Da+e1McxrVr1s4jUykp2EXsOHpTxZ4M0X8vNkXCIVSuaMp4UI0P+LQawbDA+j3FaecqqBfWMZ2sHQ8bpoA== + dependencies: + "@aws-crypto/crc32" "2.0.0" + "@aws-crypto/crc32c" "2.0.0" + "@aws-sdk/is-array-buffer" "3.55.0" + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-host-header@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.110.0.tgz#a28115e2797b86c2fb583000593b723a51313b92" + integrity sha512-/Cknn1vL2LTlclI0MX2RzmtdPlCJ5palCRXxm/mod1oHwg4oNTKRlUX3LUD+L8g7JuJ4h053Ch9KS/A0vanE5Q== + dependencies: + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-location-constraint@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.110.0.tgz#0a710ac704cc7c40ca34edf62387d8ac1fdbdaae" + integrity sha512-8ZSo9sqrTMcSp0xEJQ3ypmQpeSMQl1NXXv72khJPweZqDoO0eAbfytwyH4JH4sP0VwVVmuDHdwPXyDZX7I0iQg== + dependencies: + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-logger@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.110.0.tgz#69eb0b2d0d9833f6fdbe33eb1876254e7cee53ec" + integrity sha512-+pz+a+8dfTnzLj79nHrv3aONMp/N36/erMd+7JXeR84QEosVLrFBUwKA8x5x6O3s1iBbQzRKMYEIuja9xn1BPA== + dependencies: + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-recursion-detection@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.110.0.tgz#8daa2bc9f62cbf499d9c615726cf2a51f46e70ff" + integrity sha512-Wav782zd7bcd1e6txRob76CDOdVOaUQ8HXoywiIm/uFrEEUZvhs2mgnXjVUVCMBUehdNgnL99z420aS13JeL/Q== + dependencies: + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-retry@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-retry/-/middleware-retry-3.110.0.tgz#3bdbd66d06dcbddbdf684d1d81c6d5fd7746f03b" + integrity sha512-lwLAQQveCiUqymQvVYjCee6QOXw3Zqbc9yq+pxYdXbs1Cv1XMA6PeJeUU5r5KEVuSceBLyyrnl6E0R1l1om1MQ== + dependencies: + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/service-error-classification" "3.110.0" + "@aws-sdk/types" "3.110.0" + "@aws-sdk/util-middleware" "3.110.0" + tslib "^2.3.1" + uuid "^8.3.2" + +"@aws-sdk/middleware-sdk-s3@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.110.0.tgz#069603d33fbc349661facb0aaa131a95263e1b88" + integrity sha512-/PpZU11dkGldD6yeAccPxFd5nzofLOA3+j25RdIwz2jlJMLl9TeznYRtFH5JhHonP3lsK+IPEnFPwuL6gkBxIQ== + dependencies: + "@aws-sdk/middleware-bucket-endpoint" "3.110.0" + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/types" "3.110.0" + "@aws-sdk/util-arn-parser" "3.55.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-sdk-sts@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.110.0.tgz#8c1e34b72355c5e63495927a01839f210327f0c1" + integrity sha512-EjY/YFdlr5jECde6qIrTIyGBbn/34CKcQGKvmvRd31+3qaClIJLAwNuHfcVzWvCUGbAslsfvdbOpLju33pSQRA== + dependencies: + "@aws-sdk/middleware-signing" "3.110.0" + "@aws-sdk/property-provider" "3.110.0" + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/signature-v4" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-serde@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-serde/-/middleware-serde-3.110.0.tgz#603dcc1f68d78e9123f9b696150374a8357de6c3" + integrity sha512-brVupxgEAmcZ9cZvdHEH8zncjvGKIiud8pOe4fiimp5NpHmjBLew4jUbnOKNZNAjaidcKUtz//cxtutD6yXEww== + dependencies: + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-signing@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.110.0.tgz#8faa6acdaedb1c29614fe7ba88a74534db38f3bb" + integrity sha512-y6ZKrGYfgDlFMzWhZmoq5J1UctBgZOUvMmnU9sSeZ020IlEPiOxFMvR0Zu6TcYThp8uy3P0wyjQtGYeTl9Z/kA== + dependencies: + "@aws-sdk/property-provider" "3.110.0" + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/signature-v4" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-ssec@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.110.0.tgz#85020a0e54840e572231407dde6d40a82239d03b" + integrity sha512-Zrm+h+C+MXv2Q+mh8O/zwK2hUYM4kq4I1vx72RPpvyfIk4/F5ZzeA3LSVluISyAW+iNqS8XFvGFrzl2gB8zWsg== + dependencies: + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-stack@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-stack/-/middleware-stack-3.110.0.tgz#5a531c83ec375adf9d7f1bd80b725cebf7b2f01d" + integrity sha512-iaLHw6ctOuGa9UxNueU01Xes+15dR+mqioRpUOUZ9Zx+vhXVpD7C8lnNqhRnYeFXs10/rNIzASgsIrAHTlnlIQ== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/middleware-user-agent@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.110.0.tgz#52f32e99ecb641babcd59bb010527d5614e908f4" + integrity sha512-Y6FgiZr99DilYq6AjeaaWcNwVlSQpNGKrILzvV4Tmz03OaBIspe4KL+8EZ2YA/sAu5Lpw80vItdezqDOwGAlnQ== + dependencies: + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/node-config-provider@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/node-config-provider/-/node-config-provider-3.110.0.tgz#7d032082b85458ea4959f744d473e328be024359" + integrity sha512-46p4dCPGYctuybTQTwLpjenA1QFHeyJw/OyggGbtUJUy+833+ldnAwcPVML2aXJKUKv3APGI8vq1kaloyNku3Q== + dependencies: + "@aws-sdk/property-provider" "3.110.0" + "@aws-sdk/shared-ini-file-loader" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/node-http-handler@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/node-http-handler/-/node-http-handler-3.110.0.tgz#b29ba034558ec3cddae69860d49766a27ee73354" + integrity sha512-/rP+hY516DpP8fZhwFW5xM/ElH0w6lxw/15VvZCoY5EnOLAF5XIsJdzscWPSEW2FHCylBM4SNrKhGar14BDXhA== + dependencies: + "@aws-sdk/abort-controller" "3.110.0" + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/querystring-builder" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/property-provider@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/property-provider/-/property-provider-3.110.0.tgz#ea60c33a8e243246fc21d478ff009063825b9abd" + integrity sha512-7NkpmYeOkK3mhWBNU+/zSDqwzeaSPH1qrq4L//WV7WS/weYyE/jusQeZoOxVsuZQnQEXHt5O2hKVeUwShl12xA== + dependencies: + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/protocol-http@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/protocol-http/-/protocol-http-3.110.0.tgz#ff3cffa5b1eb7c8564a9e9019a8842b429c7f85c" + integrity sha512-qdi2gCbJiyPyLn+afebPNp/5nVCRh1X7t7IRIFl3FHVEC+o54u/ojay/MLZ4M/+X9Fa4Zxsb0Wpp3T0xAHVDBg== + dependencies: + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/querystring-builder@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-builder/-/querystring-builder-3.110.0.tgz#c7f63262e898ab38cdbbbfcd03ddbfde346c9595" + integrity sha512-7V3CDXj519izmbBn9ZE68ymASwGriA+Aq+cb/yHSVtffnvXjPtvONNw7G/5iVblisGLSCUe2hSvpYtcaXozbHw== + dependencies: + "@aws-sdk/types" "3.110.0" + "@aws-sdk/util-uri-escape" "3.55.0" + tslib "^2.3.1" + +"@aws-sdk/querystring-parser@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-parser/-/querystring-parser-3.110.0.tgz#0551efb7aaa867d3b6705f62d798a45247f5f44b" + integrity sha512-//pJHH7hrhdDMZGBPKXKymmC/tJM7gFT0w/qbu/yd3Wm4W2fMB+8gkmj6EZctx7jrsWlfRQuvFejKqEfapur/g== + dependencies: + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/service-error-classification@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/service-error-classification/-/service-error-classification-3.110.0.tgz#09398517d4ad9787bd0d904816bfe0ffd68b1f5f" + integrity sha512-ccgCE0pU/4RmXR6CP3fLAdhPAve7bK/yXBbGzpSHGAQOXqNxYzOsAvQ30Jg6X+qjLHsI/HR2pLIE65z4k6tynw== + +"@aws-sdk/shared-ini-file-loader@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.110.0.tgz#f91b66e7084312df2b337cc990c9585e832fc2fc" + integrity sha512-E1ERoqEoG206XNBYWCKLgHkzCbTxdpDEGbsLET2DnvjFsT0s9p2dPvVux3bYl7JVAhyGduE+qcqWk7MzhFCBNQ== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/signature-v4-multi-region@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.110.0.tgz#5fb3e0662d7a99e9618ae9e60a460c994efd1c3e" + integrity sha512-D5nlq6em9fU9EMmpjQtLItr2d6MmfM9yofOaeNQcgY8wFJEOCc2ADccq8dCO0F4twakAvjuUIkBAWMBviiuC7Q== + dependencies: + "@aws-sdk/protocol-http" "3.110.0" + "@aws-sdk/signature-v4" "3.110.0" + "@aws-sdk/types" "3.110.0" + "@aws-sdk/util-arn-parser" "3.55.0" + tslib "^2.3.1" + +"@aws-sdk/signature-v4@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4/-/signature-v4-3.110.0.tgz#9dba5d06345fa756b4c23deeec7086f6148a5bf1" + integrity sha512-utxxdllOnmQDhbpipnFAbuQ4c2pwefZ+2hi48jKvQRULQ2PO4nxLmdZm6B0FXaTijbKsyO7GrMik+EZ6mi3ARQ== + dependencies: + "@aws-sdk/is-array-buffer" "3.55.0" + "@aws-sdk/types" "3.110.0" + "@aws-sdk/util-hex-encoding" "3.109.0" + "@aws-sdk/util-middleware" "3.110.0" + "@aws-sdk/util-uri-escape" "3.55.0" + tslib "^2.3.1" + +"@aws-sdk/smithy-client@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/smithy-client/-/smithy-client-3.110.0.tgz#397c0e7ef56ffa058469c641b586978400c09dd7" + integrity sha512-gNLYrmdAe/1hVF2Nv2LF4OkL1A0a1o708pEMZHzql9xP164omRDaLrGDhz9tH7tsJEgLz+Bf4E8nTuISeDwvGg== + dependencies: + "@aws-sdk/middleware-stack" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/types@3.110.0", "@aws-sdk/types@^3.1.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.110.0.tgz#09404533b507925eadf9acf9c4356667048e45bd" + integrity sha512-dLVoqODU3laaqNFPyN1QLtlQnwX4gNPMXptEBIt/iJpuZf66IYJe6WCzVZGt4Zfa1CnUmrlA428AzdcA/KCr2A== + +"@aws-sdk/url-parser@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/url-parser/-/url-parser-3.110.0.tgz#87d5c0a6f6d2f29027c747c65d8a2846302bc792" + integrity sha512-tILFB8/Q73yzgO0dErJNnELmmBszd0E6FucwAnG3hfDefjqCBe09Q/1yhu2aARXyRmZa4AKp0sWcdwIWHc8dnA== + dependencies: + "@aws-sdk/querystring-parser" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/util-arn-parser@3.55.0": + version "3.55.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.55.0.tgz#6672eb2975e798a460bedfaf6b5618d4e4b262e1" + integrity sha512-76KJxp4MRWufHYWys7DFl64znr5yeJ3AIQNAPCKKw1sP0hzO7p6Kx0PaJnw9x+CPSzOrT4NbuApL6/srYhKDGg== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-base64-browser@3.109.0": + version "3.109.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-base64-browser/-/util-base64-browser-3.109.0.tgz#e7faf5c4cbb88bc39b9c1c5a1a79e4c869e9f645" + integrity sha512-lAZ6fyDGiRLaIsKT9qh7P9FGuNyZ4gAbr1YOSQk/5mHtaTuUvxlPptZuInNM/0MPQm6lpcot00D8IWTucn4PbA== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-base64-node@3.55.0": + version "3.55.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-base64-node/-/util-base64-node-3.55.0.tgz#da9a3fd6752be49163572144793e6b23d0186ff4" + integrity sha512-UQ/ZuNoAc8CFMpSiRYmevaTsuRKzLwulZTnM8LNlIt9Wx1tpNvqp80cfvVj7yySKROtEi20wq29h31dZf1eYNQ== + dependencies: + "@aws-sdk/util-buffer-from" "3.55.0" + tslib "^2.3.1" + +"@aws-sdk/util-body-length-browser@3.55.0": + version "3.55.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.55.0.tgz#9c2637097501032f6a1afddb76687415fe9b44b6" + integrity sha512-Ei2OCzXQw5N6ZkTMZbamUzc1z+z1R1Ja5tMEagz5BxuX4vWdBObT+uGlSzL8yvTbjoPjnxWA2aXyEqaUP3JS8Q== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-body-length-node@3.55.0": + version "3.55.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-body-length-node/-/util-body-length-node-3.55.0.tgz#67049bbb6c62d794a1bb5a13b9a678988c925489" + integrity sha512-lU1d4I+9wJwydduXs0SxSfd+mHKjxeyd39VwOv6i2KSwWkPbji9UQqpflKLKw+r45jL7+xU/zfeTUg5Tt/3Gew== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-buffer-from@3.55.0": + version "3.55.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-buffer-from/-/util-buffer-from-3.55.0.tgz#e7c927974b07a29502aa1ad58509b91d0d7cf0f7" + integrity sha512-uVzKG1UgvnV7XX2FPTylBujYMKBPBaq/qFBxfl0LVNfrty7YjpfieQxAe6yRLD+T0Kir/WDQwGvYC+tOYG3IGA== + dependencies: + "@aws-sdk/is-array-buffer" "3.55.0" + tslib "^2.3.1" + +"@aws-sdk/util-config-provider@3.109.0": + version "3.109.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-config-provider/-/util-config-provider-3.109.0.tgz#7828b8894b2b23c289ffc5c106cbced7a5d6ee86" + integrity sha512-GrAZl/aBv0A28LkyNyq8SPJ5fmViCwz80fWLMeWx/6q5AbivuILogjlWwEZSvZ9zrlHOcFC0+AnCa5pQrjaslw== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-defaults-mode-browser@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.110.0.tgz#b72331874da2c5e8a366cd98828a06fe19b52ae5" + integrity sha512-Y2dcOOD20S3bv/IjUqpdKIiDt6995SXNG5Pu/LeSdXNyLCOIm9rX4gHTxl9fC1KK5M/gR9fGJ362f67WwqEEqw== + dependencies: + "@aws-sdk/property-provider" "3.110.0" + "@aws-sdk/types" "3.110.0" + bowser "^2.11.0" + tslib "^2.3.1" + +"@aws-sdk/util-defaults-mode-node@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.110.0.tgz#52b4c84fc7aa06838ea6bb29d216a2d7615b9036" + integrity sha512-Cr3Z5nyrw1KowjbW76xp8hkT/zJtYjAVZ9PS4l84KxIicbVvDOBpxG3yNddkuQcavmlH6G4wH9uM5DcnpKDncg== + dependencies: + "@aws-sdk/config-resolver" "3.110.0" + "@aws-sdk/credential-provider-imds" "3.110.0" + "@aws-sdk/node-config-provider" "3.110.0" + "@aws-sdk/property-provider" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/util-hex-encoding@3.109.0": + version "3.109.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.109.0.tgz#93b20acc27c0a1d7d80f653bf19d3dd01c2ccc65" + integrity sha512-s8CgTNrn3cLkrdiohfxLuOYPCanzvHn/aH5RW6DaMoeQiG5Hl9QUiP/WtdQ9QQx3xvpQFpmvxIaSBwSgFNLQxA== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-locate-window@^3.0.0": + version "3.55.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.55.0.tgz#a4136a20ee1bfcb73967a6614caf769ef79db070" + integrity sha512-0sPmK2JaJE2BbTcnvybzob/VrFKCXKfN4CUKcvn0yGg/me7Bz+vtzQRB3Xp+YSx+7OtWxzv63wsvHoAnXvgxgg== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-middleware@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-middleware/-/util-middleware-3.110.0.tgz#00a727273974f54424954235867c1ddb0f6dad56" + integrity sha512-PTVWrI5fA9d5hHJs6RzX2dIS2jRQ3uW073Fm0BePpQeDdZrEk+S5KNwRhUtpN6sdSV45vm6S9rrjZUG51qwGmA== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-stream-browser@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-stream-browser/-/util-stream-browser-3.110.0.tgz#2b39667008b447a95a6b2c1dceaf99dd3807c8b3" + integrity sha512-kAMrHtgrhr6ODRnzt/V+LSDVDvejcbdUp19n4My2vrPwKw3lM65vT+FAPIlGeDQBtOOhmlTbrYM3G3KKnlnHyg== + dependencies: + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/util-stream-node@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-stream-node/-/util-stream-node-3.110.0.tgz#83089ff4c4b7dd6abaf6a489375cbd44765f4fb0" + integrity sha512-jgkO7aLRpE3EUqU5XUdo0FmlyBVCFHKyHd/jdEN8h9+XMa44rl2QMdOSFQtwaNI4NC8J+OC66u2dQ+8QQnOLig== + dependencies: + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/util-uri-escape@3.55.0": + version "3.55.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-uri-escape/-/util-uri-escape-3.55.0.tgz#ee57743c628a1c9f942dfe73205ce890ec011916" + integrity sha512-mmdDLUpFCN2nkfwlLdOM54lTD528GiGSPN1qb8XtGLgZsJUmg3uJSFIN2lPeSbEwJB3NFjVas/rnQC48i7mV8w== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-user-agent-browser@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.110.0.tgz#e0643e6047aab5137540259a42fbfdc37ae4abee" + integrity sha512-rNdhmHDMV5dNJctqlBWimkZLJRB+x03DB+61pm+SKSFk6gPIVIvc1WNXqDFphkiswT4vA13ZUkGHzt+N4+noQQ== + dependencies: + "@aws-sdk/types" "3.110.0" + bowser "^2.11.0" + tslib "^2.3.1" + +"@aws-sdk/util-user-agent-node@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.110.0.tgz#750abd6bb14f25a11e09d764f724b0d0e1c1248b" + integrity sha512-OQ915TPCCBwZWz5Np8zkNWn7U6KvrTZfFoCOy/VIemK3dUqmnBZ7HqGpuZx8SwJ2R9JE1x+j0niYSJ5fWJZZKA== + dependencies: + "@aws-sdk/node-config-provider" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/util-utf8-browser@3.109.0", "@aws-sdk/util-utf8-browser@^3.0.0": + version "3.109.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.109.0.tgz#d013272e1981b23a4c84ac06f154db686c0cf84e" + integrity sha512-FmcGSz0v7Bqpl1SE8G1Gc0CtDpug+rvqNCG/szn86JApD/f5x8oByjbEiAyTU2ZH2VevUntx6EW68ulHyH+x+w== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-utf8-node@3.109.0": + version "3.109.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-node/-/util-utf8-node-3.109.0.tgz#89e06d916f5b246c7265f59bac742973ac0767ac" + integrity sha512-Ti/ZBdvz2eSTElsucjzNmzpyg2MwfD1rXmxD0hZuIF8bPON/0+sZYnWd5CbDw9kgmhy28dmKue086tbZ1G0iLQ== + dependencies: + "@aws-sdk/util-buffer-from" "3.55.0" + tslib "^2.3.1" + +"@aws-sdk/util-waiter@3.110.0": + version "3.110.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-waiter/-/util-waiter-3.110.0.tgz#fa1321024f4ffb270f4b09b703802b1730220f0e" + integrity sha512-8dE6W6XYfjk1gx/aeb8NeLfMMLkLFhlV1lmKpFSBJhY8msajU8aQahTuykq5JW8QT/wCGbqbu7dH35SdX7kO+A== + dependencies: + "@aws-sdk/abort-controller" "3.110.0" + "@aws-sdk/types" "3.110.0" + tslib "^2.3.1" + +"@aws-sdk/xml-builder@3.109.0": + version "3.109.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.109.0.tgz#dd2d3bf59d29a1c2968f477ec16680d47d81d921" + integrity sha512-+aAXynnrqya1Eukz4Gxch4xIXCZolIMWGD4Ll/Q5yXT5uAjGh2HQWd9J0LWE+gYChpWetZbAVYZ3cEJ6F+SpZA== + dependencies: + tslib "^2.3.1" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + +"@babel/compat-data@^7.17.10": + version "7.18.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.5.tgz#acac0c839e317038c73137fbb6ef71a1d6238471" + integrity sha512-BxhE40PVCBxVEJsSBhB6UWyAuqJRxGsAw8BdHMJ3AKGydcwuWW4kOO3HmqBQAdcq/OP+/DlTVxLvsCzRTnZuGg== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3": + version "7.18.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.5.tgz#c597fa680e58d571c28dda9827669c78cdd7f000" + integrity sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-compilation-targets" "^7.18.2" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helpers" "^7.18.2" + "@babel/parser" "^7.18.5" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.18.5" + "@babel/types" "^7.18.4" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/generator@^7.18.2", "@babel/generator@^7.7.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d" + integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== + dependencies: + "@babel/types" "^7.18.2" + "@jridgewell/gen-mapping" "^0.3.0" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b" + integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ== + dependencies: + "@babel/compat-data" "^7.17.10" + "@babel/helper-validator-option" "^7.16.7" + browserslist "^4.20.2" + semver "^6.3.0" + +"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd" + integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ== + +"@babel/helper-function-name@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" + integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== + dependencies: + "@babel/template" "^7.16.7" + "@babel/types" "^7.17.0" + +"@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-module-imports@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-module-transforms@^7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd" + integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.18.0" + "@babel/types" "^7.18.0" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" + integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== + +"@babel/helper-simple-access@^7.17.7": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9" + integrity sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ== + dependencies: + "@babel/types" "^7.18.2" + +"@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + +"@babel/helper-validator-option@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" + integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== + +"@babel/helpers@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384" + integrity sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg== + dependencies: + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.18.2" + "@babel/types" "^7.18.2" + +"@babel/highlight@^7.16.7": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.12.tgz#257de56ee5afbd20451ac0a75686b6b404257351" + integrity sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.18.5": + version "7.18.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.5.tgz#337062363436a893a2d22faa60be5bb37091c83c" + integrity sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz#b54fc3be6de734a56b87508f99d6428b5b605a7b" + integrity sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw== + dependencies: + "@babel/helper-plugin-utils" "^7.17.12" + +"@babel/template@^7.16.7", "@babel/template@^7.3.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.18.5", "@babel/traverse@^7.7.2": + version "7.18.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.5.tgz#94a8195ad9642801837988ab77f36e992d9a20cd" + integrity sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-environment-visitor" "^7.18.2" + "@babel/helper-function-name" "^7.17.9" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.18.5" + "@babel/types" "^7.18.4" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.18.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" + integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@commitlint/config-validator@^17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/config-validator/-/config-validator-17.0.0.tgz#49ab09f3ca0ac3449e79ea389cb4942423162ac0" + integrity sha512-78IQjoZWR4kDHp/U5y17euEWzswJpPkA9TDL5F6oZZZaLIEreWzrDZD5PWtM8MsSRl/K2LDU/UrzYju2bKLMpA== + dependencies: + "@commitlint/types" "^17.0.0" + ajv "^6.12.6" + +"@commitlint/execute-rule@^17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/execute-rule/-/execute-rule-17.0.0.tgz#186e9261fd36733922ae617497888c4bdb6e5c92" + integrity sha512-nVjL/w/zuqjCqSJm8UfpNaw66V9WzuJtQvEnCrK4jDw6qKTmZB+1JQ8m6BQVZbNBcwfYdDNKnhIhqI0Rk7lgpQ== + +"@commitlint/load@>6.1.1": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/load/-/load-17.0.0.tgz#0bbefe6d8b99276714c5ea8ef32de2bd2f082698" + integrity sha512-XaiHF4yWQOPAI0O6wXvk+NYLtJn/Xb7jgZEeKd4C1ZWd7vR7u8z5h0PkWxSr0uLZGQsElGxv3fiZ32C5+q6M8w== + dependencies: + "@commitlint/config-validator" "^17.0.0" + "@commitlint/execute-rule" "^17.0.0" + "@commitlint/resolve-extends" "^17.0.0" + "@commitlint/types" "^17.0.0" + "@types/node" ">=12" + chalk "^4.1.0" + cosmiconfig "^7.0.0" + cosmiconfig-typescript-loader "^2.0.0" + lodash "^4.17.19" + resolve-from "^5.0.0" + typescript "^4.6.4" + +"@commitlint/resolve-extends@^17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/resolve-extends/-/resolve-extends-17.0.0.tgz#3a40ee08184b984acf475ebc962641f435e3a639" + integrity sha512-wi60WiJmwaQ7lzMXK8Vbc18Hq9tE2j/6iv2AFfPUGV7fvfY6Sf1iNKuUHirSqR0fquUyufIXe4y/K9A6LVIIvw== + dependencies: + "@commitlint/config-validator" "^17.0.0" + "@commitlint/types" "^17.0.0" + import-fresh "^3.0.0" + lodash "^4.17.19" + resolve-from "^5.0.0" + resolve-global "^1.0.0" + +"@commitlint/types@^17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/types/-/types-17.0.0.tgz#3b4604c1a0f06c340ce976e6c6903d4f56e3e690" + integrity sha512-hBAw6U+SkAT5h47zDMeOu3HSiD0SODw4Aq7rRNh1ceUmL7GyLKYhPbUvlRWqZ65XjBLPHZhFyQlRaPNz8qvUyQ== + dependencies: + chalk "^4.1.0" + +"@cspell/cspell-bundled-dicts@^6.1.2": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-6.1.2.tgz#40a2af61152f5e68097be206dbb0dfc512f79e32" + integrity sha512-vMS15jKPNH93Fv0bu/lrTSmXKt6hDCEEwOIyajO84cJMuKRVuR9Vw0ZtkFsVAwHyTNZ8mGxposQ20TbAL/SUlw== + dependencies: + "@cspell/dict-ada" "^2.0.0" + "@cspell/dict-aws" "^2.0.0" + "@cspell/dict-bash" "^2.0.3" + "@cspell/dict-companies" "^2.0.5" + "@cspell/dict-cpp" "^3.1.0" + "@cspell/dict-cryptocurrencies" "^2.0.0" + "@cspell/dict-csharp" "^3.0.1" + "@cspell/dict-css" "^2.0.0" + "@cspell/dict-dart" "^1.1.1" + "@cspell/dict-django" "^2.0.0" + "@cspell/dict-docker" "^1.1.0" + "@cspell/dict-dotnet" "^2.0.1" + "@cspell/dict-elixir" "^2.0.1" + "@cspell/dict-en-gb" "^1.1.33" + "@cspell/dict-en_us" "^2.2.5" + "@cspell/dict-filetypes" "^2.0.1" + "@cspell/dict-fonts" "^2.0.0" + "@cspell/dict-fullstack" "^2.0.6" + "@cspell/dict-git" "^1.0.1" + "@cspell/dict-golang" "^3.0.1" + "@cspell/dict-haskell" "^2.0.0" + "@cspell/dict-html" "^3.0.1" + "@cspell/dict-html-symbol-entities" "^3.0.0" + "@cspell/dict-java" "^3.0.2" + "@cspell/dict-latex" "^2.0.5" + "@cspell/dict-lorem-ipsum" "^2.0.0" + "@cspell/dict-lua" "^2.0.0" + "@cspell/dict-node" "^3.0.1" + "@cspell/dict-npm" "^3.0.1" + "@cspell/dict-php" "^2.0.0" + "@cspell/dict-powershell" "^2.0.0" + "@cspell/dict-public-licenses" "^1.0.5" + "@cspell/dict-python" "^3.0.6" + "@cspell/dict-r" "^1.0.3" + "@cspell/dict-ruby" "^2.0.1" + "@cspell/dict-rust" "^2.0.0" + "@cspell/dict-scala" "^2.0.0" + "@cspell/dict-software-terms" "^2.1.8" + "@cspell/dict-swift" "^1.0.3" + "@cspell/dict-typescript" "^2.0.0" + "@cspell/dict-vue" "^2.0.2" + +"@cspell/cspell-pipe@^6.1.2": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@cspell/cspell-pipe/-/cspell-pipe-6.1.2.tgz#8c2cc03c5e146a0eec4b03f936d6598f6509680e" + integrity sha512-QPbRsumSbu2h6Sdls2bv6FeLFBvs+XSSOmBwVXTaRu6Vl0hEi3P69BiHIWTYQqWTe2NYZnW8lpXUh5/J8/nolw== + +"@cspell/cspell-types@^6.1.2": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@cspell/cspell-types/-/cspell-types-6.1.2.tgz#5689a6656160052d5a9d60f0d2316c894e885a09" + integrity sha512-EENGQ469e3mTpSWfMF/GS29eOAAONiavdVF/uiV8kcxf8SqfkMJvVjFZ1w0KgC80pnCVUzRzMBO4HKmXPj6Ncg== + +"@cspell/dict-ada@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-ada/-/dict-ada-2.0.0.tgz#5d31967cbd7a0d12f4b4de3fd5b09e59239cf78b" + integrity sha512-4gfJEYXVwz6IN2LBaT6QoUV4pqaR35i0z0u9O684vLuVczvNJIHa4vNaSEFBr9d6xxncUyqstgP9P73ajJjh9A== + +"@cspell/dict-aws@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-aws/-/dict-aws-2.0.0.tgz#9af72af4e59e96029dd4335271d87784843cb7dd" + integrity sha512-NKz7pDZ7pwj/b33i3f4WLpC1rOOUMmENwYgftxU+giU2YBeKM2wZbMTSEIzsrel56r0UlQYmdIVlP/B4nnVaoQ== + +"@cspell/dict-bash@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@cspell/dict-bash/-/dict-bash-2.0.3.tgz#a54d6b8899569e348fcd33c95daaeef42075d75b" + integrity sha512-iw78lmxm49q2LhHTQCSu9zs85E8Sm6ui82OvxajU9rdhckFzZoj/KCQi9P0gFuL+w3WmQObHqdH2/sxK4oi2wA== + +"@cspell/dict-companies@^2.0.5": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@cspell/dict-companies/-/dict-companies-2.0.6.tgz#55bd1c0bb48cd10682ea023506503fcba82c3de7" + integrity sha512-S1U+ZqvwDwiMYEFPKNxRmH0z7YlmOj93xadga4U0LMa3S4ORSf192uMB0w0AFBwXUPnXHM5uqIGP0LTt0b4Ygg== + +"@cspell/dict-cpp@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-cpp/-/dict-cpp-3.1.0.tgz#0b2983dbd942ae77c7cf0b349786344a1ba7974e" + integrity sha512-lav99zUQ+iPq6dkQRnTN0+KE9th0UG6Nwl34afyEGJ8CN5Dcq/RJjCVvOkLw6vPvs505xrvQcZW1huftQK8WVg== + +"@cspell/dict-cryptocurrencies@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-2.0.0.tgz#a74eecb42a46a96d08b6613fdb5c554529d3afff" + integrity sha512-nREysmmfOp7L2YCRAUufQahwD5/Punzb5AZ6eyg4zUamdRWHgBFphb5/9h2flt1vgdUfhc6hZcML21Ci7iXjaA== + +"@cspell/dict-csharp@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@cspell/dict-csharp/-/dict-csharp-3.0.1.tgz#94a673e02bb7cc03c25bc699bc1b5bd786c2c5fd" + integrity sha512-xkfQu03F388w4sdVQSSjrVMkxAxpTYB2yW7nw0XYtTjl3L/jBgvTr/j1BTjdFbQhdNf10Lg0Ak1kXOjmHodVqA== + +"@cspell/dict-css@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-css/-/dict-css-2.0.0.tgz#91dca013f16b51144eaea160e144b830f2dad027" + integrity sha512-MrFyswFHnPh4H0u6IlV4eHy+ZCUrrHzeL161LyTOqCvaKpbZavMgNYXzZqTF9xafO0iLgwKrl+Gkclu1KVBg0Q== + +"@cspell/dict-dart@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@cspell/dict-dart/-/dict-dart-1.1.1.tgz#d4da9cf72e5df369b6d9ebe588f9c1474adf3556" + integrity sha512-XBOCpezXrgFN18kGEwqMpTUGZdw4BjCoJrNOo6qBdcdZySCrEHLwELraLOkcSba2kM4stmTp0t59FkwtP8TKOA== + +"@cspell/dict-django@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-django/-/dict-django-2.0.0.tgz#a5f5f693a686e5873f9dfb547ee3b3142ef760b1" + integrity sha512-GkJdJv6cmzrKcmq2/oxTXjKF5uv71r4eTqnFmgPbNBW1t+G4VYpzOf0QrVQrhx2RC4DdW5XfcTf+iS0FxHOTmw== + +"@cspell/dict-docker@^1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@cspell/dict-docker/-/dict-docker-1.1.1.tgz#f9cc1c732a9be2f31a5a4aa079d045798b97b01d" + integrity sha512-UEYoeRDm7oUN9yz1mYSozz6D4+2N14S/cd2Re9et6Xzq6yi62s4ky3knF92Of2weelADjnN41UA22VBhRAf7Sw== + +"@cspell/dict-dotnet@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@cspell/dict-dotnet/-/dict-dotnet-2.0.1.tgz#8ef56df758b63f0a2ba4d8681a427a6861ed34d5" + integrity sha512-b1n4crJRW0WZVf9Gp/52j/tDtjYiZ3N81fIyfqPlBrjsh/5AivfA697DYwQ2mr8ngNX7RsqRtYNQjealA1rEnQ== + +"@cspell/dict-elixir@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@cspell/dict-elixir/-/dict-elixir-2.0.1.tgz#1a9b422215b5edabb84568cfa5c0c70bc164a2ce" + integrity sha512-eTTTxZt1FqGkM780yFDxsGHvTbWqvlK8YISSccK8FyrB6ULW+uflQlNS5AnWg3uWKC48b7pQott+odYCsPJ+Ow== + +"@cspell/dict-en-gb@^1.1.33": + version "1.1.33" + resolved "https://registry.yarnpkg.com/@cspell/dict-en-gb/-/dict-en-gb-1.1.33.tgz#7f1fd90fc364a5cb77111b5438fc9fcf9cc6da0e" + integrity sha512-tKSSUf9BJEV+GJQAYGw5e+ouhEe2ZXE620S7BLKe3ZmpnjlNG9JqlnaBhkIMxKnNFkLY2BP/EARzw31AZnOv4g== + +"@cspell/dict-en_us@^2.2.5": + version "2.2.6" + resolved "https://registry.yarnpkg.com/@cspell/dict-en_us/-/dict-en_us-2.2.6.tgz#13ce28c12a29620d3996d1015c8aa20060dd62b6" + integrity sha512-TJ4edLus8TV6Tr7ceOxHG5ZV2MhKJioteNT9jhdcSTdySsfQJjDAx6AIGiVVeRu5s9yR61oL5In7UyMCA80RWQ== + +"@cspell/dict-filetypes@^2.0.1": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@cspell/dict-filetypes/-/dict-filetypes-2.0.2.tgz#4b05efbbaeab86f4f69c995d52c4b76803ed2ec0" + integrity sha512-do7/Iwxjx+FHybe6UTocsWNRF1ar4cwhQoV2K2YzYTm73CoU5LMEwi2LY0Mwp/mn90TKbpPPQGCJ0sRpvaZ4AA== + +"@cspell/dict-fonts@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@cspell/dict-fonts/-/dict-fonts-2.0.1.tgz#16a99591fef8ba616d09db8973494e9d9ecdd3c9" + integrity sha512-UX9evXp7IPZ55G1NxVLADoRgGa+6MPSFx4zWveT6vHIVI9k50yZZY2QkIgQIOEPgk8RNrNe1Tf6HLfndgFL2kQ== + +"@cspell/dict-fullstack@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@cspell/dict-fullstack/-/dict-fullstack-2.0.6.tgz#0bec93306cba070ed6aa0b619d8080c86310ab5d" + integrity sha512-R2E2xvbHvvRwwurxfpBJDRIJjXBMfEPF5WNV3LTOEMRqkZtoYCeJK9aqc8LHlmJMtAbnN1cx//BCDIyTJ0rO0A== + +"@cspell/dict-git@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@cspell/dict-git/-/dict-git-1.0.1.tgz#9de5ab2532abcdc8b10bd83ccb1f5e5dae0b6067" + integrity sha512-Rk+eTof/9inF11lvxmkCRK+gODatA3qai8kSASv6OG/JfPvpj7fTHErx/rdgPw/LOTDUafnoTjTYmj7B2MOQXg== + +"@cspell/dict-golang@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@cspell/dict-golang/-/dict-golang-3.0.1.tgz#acde95eb340c4512d132586a8326b1b3d971c0f7" + integrity sha512-0KNfXTbxHW2l8iVjxeOf+KFv9Qrw3z5cyKnkuYJWlBTSB5KcUBfeKCb4fsds26VdANqiy6U91b4gDx5kNEmBjQ== + +"@cspell/dict-haskell@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-haskell/-/dict-haskell-2.0.0.tgz#9e7e58eba2b4633221650dcdcc43f73588b48119" + integrity sha512-cjX1Br+gSWqtcmJD/IMHz1UoP3pUaKIIKy/JfhEs7ANtRt6hhfEKe9dl2kQzDkkKt4pXol+YgdYxL/sVc/nLgQ== + +"@cspell/dict-html-symbol-entities@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-3.0.0.tgz#55d5d96c37ecbde00492c4238e229908eea9cedb" + integrity sha512-04K7cPTcbYXmHICfiob4gZA1yaj4hpfM+Nl5WIJ1EAZsSGHdqmGEF28GuCjyQ8ZeKiJAsPt/vXuLBbjxkHqZyQ== + +"@cspell/dict-html@^3.0.1": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@cspell/dict-html/-/dict-html-3.0.2.tgz#acd0a06a9adcbff85d1ed6044c0636fb5294b078" + integrity sha512-ugMVQHZTvpYA/w8/E2dbSx2hdfFU9y91Omx40VUC6cNyF7jx00VKueK6gcRF3QZoB1PUhjla2YzxqRxuXI908A== + +"@cspell/dict-java@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@cspell/dict-java/-/dict-java-3.0.3.tgz#687dd9d85f05e99cf687d3fb29e9f6ea72dc73c7" + integrity sha512-aARF22BmO03YgV0robADNVf32KnvF/wMMoByYQk4IaQWh8kJ1s///S44aY/4n/Cg2tX/1kNa60VMkdCNFD7FPw== + +"@cspell/dict-latex@^2.0.5": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@cspell/dict-latex/-/dict-latex-2.0.6.tgz#9ebbb8087b0a0cee5e7e784633a17ce7c491c472" + integrity sha512-DCe/YlUMnY+/BaaHLIs2LYPgpWF4to5V9lggEkJy4CsHyD0WPqV4JpoaOMrcsK/jbUrD39T91NruwlcPJoo7xQ== + +"@cspell/dict-lorem-ipsum@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-2.0.0.tgz#47f2a9ec24808cdf8417457ae8f5a588f33c338c" + integrity sha512-jKogAKtqvgPMleL6usyj3rZ0m8sVUR6drrD+wMnWSfdx1BmUyTsYiuh/mPEfLAebaYHELWSLQG3rDZRvV9Riqg== + +"@cspell/dict-lua@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-lua/-/dict-lua-2.0.0.tgz#b96d0363a28ac7e0483ad03edb21705c4f951459" + integrity sha512-7WUEBEspSKtsq104WdIys1+DLqAxpJPzw74Py1TuE3fI5GvlzeSZkRFP2ya54GB2lCO4C3mq4M8EnitpibVDfw== + +"@cspell/dict-node@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@cspell/dict-node/-/dict-node-3.0.1.tgz#a6ee043f5dc044391e5ecc4f293497f9d96d48e1" + integrity sha512-sK2cpuV0EAc43Amd5xeQXkI9MeRTECMw+yjap06gKSModbgI7BqJUHeKZed+0Hii+LpaJ4TYpLGiRVsO+qSk0w== + +"@cspell/dict-npm@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@cspell/dict-npm/-/dict-npm-3.0.1.tgz#91cf3e263d17e0ef80117e76936559e2d16613f3" + integrity sha512-ZfuzFwE03WwyShwvQfXhhKIrFxgAkOtA/N1KdEwfP//nVDgysJfGueBhJJfI6vjUSr1IA+u5DXrSV0nowLAEhg== + +"@cspell/dict-php@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-php/-/dict-php-2.0.0.tgz#5d42f7df7c1da89fe19c2ccfe1bf61231d183990" + integrity sha512-29WgU77eTO985LvMHwPi1pcpfopfCWfTdffDyqya0JIfOSaFUrlYKzGPkE4mRxcz2G3hXsaM0SRvBNdIRwEdUg== + +"@cspell/dict-powershell@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-powershell/-/dict-powershell-2.0.0.tgz#6e8ae7381b1928dfaf8f5a625f8fae6e8d93f224" + integrity sha512-6uvEhLiGmG3u9TFkM1TYcky6aL9Yk7Sk3KJwoTYBaQJY2KqrprgyQtW6yxIw9oU52VRHlq3KKvSAA9Q26+SIkQ== + +"@cspell/dict-public-licenses@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@cspell/dict-public-licenses/-/dict-public-licenses-1.0.5.tgz#b4eeb08107b83966913689fcb09f495da249233d" + integrity sha512-N9bttzzhmCq/BN/TeP43075kj9TeaR8l9v0SPre05BRWsChVrWuMM1UvsT4ADXnsYJNl1xcn+q191S/fIzQhBg== + +"@cspell/dict-python@^3.0.6": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@cspell/dict-python/-/dict-python-3.0.6.tgz#884f398e053a5d500adc9de47d1f1049a7afcc9c" + integrity sha512-tzxJ4sd9ZGhAUKg/WJJpQGDNtoHvM8Wn+iS2+PnQj2/LTHBW4mnaCogsGsBtYu8C4b2+BEQs+tc5808AeEfLug== + +"@cspell/dict-r@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@cspell/dict-r/-/dict-r-1.0.3.tgz#1480016695ee119cf63fa8c71b161d033bbb9029" + integrity sha512-u2qeXd4cx/TvTVcmkvA+sK6f4K1uMAMO6QPMSr1pSvqGElPRP1mIBXmuiSuBzLO3LbsJuUEHw5Cp3/bxIB6rNA== + +"@cspell/dict-ruby@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@cspell/dict-ruby/-/dict-ruby-2.0.1.tgz#1cbd4d8803428bd421a5562b2d2bb4b3bae80bce" + integrity sha512-qGqhYfFeoBOashv/l0Kj5o4ilyvfq0s+t+r32juPOkOnbHz+hzxnJo2tMMg/L/UdjVV7Y8ovg4LDBC/seVrMYQ== + +"@cspell/dict-rust@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@cspell/dict-rust/-/dict-rust-2.0.1.tgz#2923392a41784e76ee00b9456d581f86a83097fb" + integrity sha512-ATDpIh0VWpQdUIZa8zqqJY4wQz3q00BTXlQCodeOmObYSb23+L6KWWzJ8mKLgpbc1lqTkogWrqxiCxlrCmqNmg== + +"@cspell/dict-scala@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-scala/-/dict-scala-2.0.0.tgz#b8098103bb03a13406c1c79f1769052353aafac4" + integrity sha512-MUwA2YKpqaQOSR4V1/CVGRNk8Ii5kf6I8Ch+4/BhRZRQXuwWbi21rDRYWPqdQWps7VNzAbbMA+PQDWsD5YY38g== + +"@cspell/dict-software-terms@^2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@cspell/dict-software-terms/-/dict-software-terms-2.1.8.tgz#2db1d3ce7c125552e86503bb09379041787e1a07" + integrity sha512-D9ECefkdbr5B0yLimy7nmEBl3AHPsweMG1wHatlCIT9uFwwqaq5e+ngbYrntEhMa6afkYY+LGOLbZ1L1dfpLVg== + +"@cspell/dict-swift@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@cspell/dict-swift/-/dict-swift-1.0.3.tgz#b819da0ca2c5dfecdd61bec55181636a06d23677" + integrity sha512-yOBLSaRD0AnkkkndJ8PuB82Evp6lA2xItf2AWsnPfCCgxp5Ojk6uUBC/WQBSkzkCAOGbXyHsu9D97tsOx2c6cw== + +"@cspell/dict-typescript@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@cspell/dict-typescript/-/dict-typescript-2.0.0.tgz#c1ce88dcb1b480623eb537670d11844047539a53" + integrity sha512-WFBahxsnD2y4Os14tE5Zxh31Ggn4DzGOAu3UoxYl1lLLxaszx4RH7LmAeFuznySboiaBeRBbpfJOjQA796O6VQ== + +"@cspell/dict-vue@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@cspell/dict-vue/-/dict-vue-2.0.2.tgz#8618b9f4825b3d80e1788082c19ac9c15832463e" + integrity sha512-/MB0RS0Gn01s4pgmjy0FvsLfr3RRMrRphEuvTRserNcM8XVtoIVAtrjig/Gg0DPwDrN8Clm0L1j7iQay6S8D0g== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@dabh/diagnostics@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" + integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + +"@eslint/eslintrc@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" + integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.3.2" + globals "^13.15.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@faker-js/faker@^7.3.0": + version "7.3.0" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-7.3.0.tgz#a508df35ded585c4e071cb5d9d7c89623c837fae" + integrity sha512-1W0PZezq2rxlAssoWemi9gFRD8IQxvf0FPL5Km3TOmGHFG7ib0TbFBJ0yC7D/1NsxunjNTK6WjUXV8ao/mKZ5w== + +"@fast-csv/format@4.3.5": + version "4.3.5" + resolved "https://registry.yarnpkg.com/@fast-csv/format/-/format-4.3.5.tgz#90d83d1b47b6aaf67be70d6118f84f3e12ee1ff3" + integrity sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A== + dependencies: + "@types/node" "^14.0.1" + lodash.escaperegexp "^4.1.2" + lodash.isboolean "^3.0.3" + lodash.isequal "^4.5.0" + lodash.isfunction "^3.0.9" + lodash.isnil "^4.0.0" + +"@fast-csv/parse@4.3.6": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@fast-csv/parse/-/parse-4.3.6.tgz#ee47d0640ca0291034c7aa94039a744cfb019264" + integrity sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA== + dependencies: + "@types/node" "^14.0.1" + lodash.escaperegexp "^4.1.2" + lodash.groupby "^4.6.0" + lodash.isfunction "^3.0.9" + lodash.isnil "^4.0.0" + lodash.isundefined "^3.0.1" + lodash.uniq "^4.5.0" + +"@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + +"@humanwhocodes/config-array@^0.9.2": + version "0.9.5" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" + integrity sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@hutson/parse-repository-url@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" + integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== + +"@isaacs/string-locale-compare@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" + integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-28.1.1.tgz#305f8ca50b6e70413839f54c0e002b60a0f2fd7d" + integrity sha512-0RiUocPVFEm3WRMOStIHbRWllG6iW6E3/gUPnf4lkrVFyXIIDeCe+vlKeYyFOMhB2EPE6FLFCNADSOOQMaqvyA== + dependencies: + "@jest/types" "^28.1.1" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^28.1.1" + jest-util "^28.1.1" + slash "^3.0.0" + +"@jest/core@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-28.1.1.tgz#086830bec6267accf9af5ca76f794858e9f9f092" + integrity sha512-3pYsBoZZ42tXMdlcFeCc/0j9kOlK7MYuXs2B1QbvDgMoW1K9NJ4G/VYvIbMb26iqlkTfPHo7SC2JgjDOk/mxXw== + dependencies: + "@jest/console" "^28.1.1" + "@jest/reporters" "^28.1.1" + "@jest/test-result" "^28.1.1" + "@jest/transform" "^28.1.1" + "@jest/types" "^28.1.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^28.0.2" + jest-config "^28.1.1" + jest-haste-map "^28.1.1" + jest-message-util "^28.1.1" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.1" + jest-resolve-dependencies "^28.1.1" + jest-runner "^28.1.1" + jest-runtime "^28.1.1" + jest-snapshot "^28.1.1" + jest-util "^28.1.1" + jest-validate "^28.1.1" + jest-watcher "^28.1.1" + micromatch "^4.0.4" + pretty-format "^28.1.1" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-28.1.1.tgz#c4cbf85283278d768f816ebd1a258ea6f9e39d4f" + integrity sha512-9auVQ2GzQ7nrU+lAr8KyY838YahElTX9HVjbQPPS2XjlxQ+na18G113OoBhyBGBtD6ZnO/SrUy5WR8EzOj1/Uw== + dependencies: + "@jest/fake-timers" "^28.1.1" + "@jest/types" "^28.1.1" + "@types/node" "*" + jest-mock "^28.1.1" + +"@jest/expect-utils@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-28.1.1.tgz#d84c346025b9f6f3886d02c48a6177e2b0360587" + integrity sha512-n/ghlvdhCdMI/hTcnn4qV57kQuV9OTsZzH1TTCVARANKhl6hXJqLKUkwX69ftMGpsbpt96SsDD8n8LD2d9+FRw== + dependencies: + jest-get-type "^28.0.2" + +"@jest/expect@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-28.1.1.tgz#ea4fcc8504b45835029221c0dc357c622a761326" + integrity sha512-/+tQprrFoT6lfkMj4mW/mUIfAmmk/+iQPmg7mLDIFOf2lyf7EBHaS+x3RbeR0VZVMe55IvX7QRoT/2aK3AuUXg== + dependencies: + expect "^28.1.1" + jest-snapshot "^28.1.1" + +"@jest/fake-timers@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-28.1.1.tgz#47ce33296ab9d680c76076d51ddbe65ceb3337f1" + integrity sha512-BY/3+TyLs5+q87rGWrGUY5f8e8uC3LsVHS9Diz8+FV3ARXL4sNnkLlIB8dvDvRrp+LUCGM+DLqlsYubizGUjIA== + dependencies: + "@jest/types" "^28.1.1" + "@sinonjs/fake-timers" "^9.1.1" + "@types/node" "*" + jest-message-util "^28.1.1" + jest-mock "^28.1.1" + jest-util "^28.1.1" + +"@jest/globals@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-28.1.1.tgz#c0a7977f85e26279cc090d9adcdf82b8a34c4061" + integrity sha512-dEgl/6v7ToB4vXItdvcltJBgny0xBE6xy6IYQrPJAJggdEinGxCDMivNv7sFzPcTITGquXD6UJwYxfJ/5ZwDSg== + dependencies: + "@jest/environment" "^28.1.1" + "@jest/expect" "^28.1.1" + "@jest/types" "^28.1.1" + +"@jest/reporters@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-28.1.1.tgz#9389f4bb3cce4d9b586f6195f83c79cd2a1c8662" + integrity sha512-597Zj4D4d88sZrzM4atEGLuO7SdA/YrOv9SRXHXRNC+/FwPCWxZhBAEzhXoiJzfRwn8zes/EjS8Lo6DouGN5Gg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^28.1.1" + "@jest/test-result" "^28.1.1" + "@jest/transform" "^28.1.1" + "@jest/types" "^28.1.1" + "@jridgewell/trace-mapping" "^0.3.7" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^28.1.1" + jest-util "^28.1.1" + jest-worker "^28.1.1" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + terminal-link "^2.0.0" + v8-to-istanbul "^9.0.0" + +"@jest/schemas@^28.0.2": + version "28.0.2" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-28.0.2.tgz#08c30df6a8d07eafea0aef9fb222c5e26d72e613" + integrity sha512-YVDJZjd4izeTDkij00vHHAymNXQ6WWsdChFRK86qck6Jpr3DCL5W3Is3vslviRlP+bLuMYRLbdp98amMvqudhA== + dependencies: + "@sinclair/typebox" "^0.23.3" + +"@jest/source-map@^28.0.2": + version "28.0.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.0.2.tgz#914546f4410b67b1d42c262a1da7e0406b52dc90" + integrity sha512-Y9dxC8ZpN3kImkk0LkK5XCEneYMAXlZ8m5bflmSL5vrwyeUpJfentacCUg6fOb8NOpOO7hz2+l37MV77T6BFPw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.7" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-28.1.1.tgz#c6f18d1bbb01aa88925dd687872a75f8414b317a" + integrity sha512-hPmkugBktqL6rRzwWAtp1JtYT4VHwv8OQ+9lE5Gymj6dHzubI/oJHMUpPOt8NrdVWSrz9S7bHjJUmv2ggFoUNQ== + dependencies: + "@jest/console" "^28.1.1" + "@jest/types" "^28.1.1" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-28.1.1.tgz#f594ee2331df75000afe0d1ae3237630ecec732e" + integrity sha512-nuL+dNSVMcWB7OOtgb0EGH5AjO4UBCt68SLP08rwmC+iRhyuJWS9MtZ/MpipxFwKAlHFftbMsydXqWre8B0+XA== + dependencies: + "@jest/test-result" "^28.1.1" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.1" + slash "^3.0.0" + +"@jest/transform@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.1.tgz#83541f2a3f612077c8501f49cc4e205d4e4a6b27" + integrity sha512-PkfaTUuvjUarl1EDr5ZQcCA++oXkFCP9QFUkG0yVKVmNObjhrqDy0kbMpMebfHWm3CCDHjYNem9eUSH8suVNHQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^28.1.1" + "@jridgewell/trace-mapping" "^0.3.7" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.1" + jest-regex-util "^28.0.2" + jest-util "^28.1.1" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.1" + +"@jest/types@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.1.tgz#d059bbc80e6da6eda9f081f293299348bd78ee0b" + integrity sha512-vRXVqSg1VhDnB8bWcmvLzmg0Bt9CRKVgHPXqYwvWMX3TvAjeO+nRuK6+VdTKCtWOvYlmkF/HqNAL/z+N3B53Kw== + dependencies: + "@jest/schemas" "^28.0.2" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9" + integrity sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" + integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== + +"@jridgewell/set-array@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" + integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.13" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" + integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" + integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@lerna/add@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/add/-/add-5.1.4.tgz#ddd21385b0a2f427b1bf1dd193e7e56f77bb3544" + integrity sha512-kysQaV0+6aFtT0rkbaeuP6qb0vYDwo7TiC+Og4STyXxv2mHXi3F8r6Z9xXNUn8LPi29gaCmB8DLmbEGlTBM4xg== + dependencies: + "@lerna/bootstrap" "5.1.4" + "@lerna/command" "5.1.4" + "@lerna/filter-options" "5.1.4" + "@lerna/npm-conf" "5.1.4" + "@lerna/validation-error" "5.1.4" + dedent "^0.7.0" + npm-package-arg "^8.1.0" + p-map "^4.0.0" + pacote "^13.4.1" + semver "^7.3.4" + +"@lerna/bootstrap@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-5.1.4.tgz#da60d4d329884d9153b536390aeb10c944e004a4" + integrity sha512-uCP0WdxGCGAGkwcuhv2nLqLByq9WJ5yr+93A8T15xZJfQsXLtYjjlivIe35MjS77eR+krwl5uY6WmGPJ33+afg== + dependencies: + "@lerna/command" "5.1.4" + "@lerna/filter-options" "5.1.4" + "@lerna/has-npm-version" "5.1.4" + "@lerna/npm-install" "5.1.4" + "@lerna/package-graph" "5.1.4" + "@lerna/pulse-till-done" "5.1.4" + "@lerna/rimraf-dir" "5.1.4" + "@lerna/run-lifecycle" "5.1.4" + "@lerna/run-topologically" "5.1.4" + "@lerna/symlink-binary" "5.1.4" + "@lerna/symlink-dependencies" "5.1.4" + "@lerna/validation-error" "5.1.4" + "@npmcli/arborist" "5.2.0" + dedent "^0.7.0" + get-port "^5.1.1" + multimatch "^5.0.0" + npm-package-arg "^8.1.0" + npmlog "^6.0.2" + p-map "^4.0.0" + p-map-series "^2.1.0" + p-waterfall "^2.1.1" + semver "^7.3.4" + +"@lerna/changed@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-5.1.4.tgz#00e660f991f0d76104a467dbebea1c232c0ef647" + integrity sha512-XwA3+pw5keO2CyjobLN8dU7mvGbzB3FD+LtLPI/zk7UbNIbl7V6uaIkoPJIdTWwP1e6S1BnGCVsAMtwQ980gTA== + dependencies: + "@lerna/collect-updates" "5.1.4" + "@lerna/command" "5.1.4" + "@lerna/listable" "5.1.4" + "@lerna/output" "5.1.4" + +"@lerna/check-working-tree@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-5.1.4.tgz#d875eaedcf370603e7c73665ded28783ae01dcb5" + integrity sha512-yFkRmZd25viwxyyOHZd3g7k2Od2Mk0Sf15fol3h/a7P0rUMf6UaMoGo2qlyo+DS51sz+eNalMmFKLpRrDXcSSw== + dependencies: + "@lerna/collect-uncommitted" "5.1.4" + "@lerna/describe-ref" "5.1.4" + "@lerna/validation-error" "5.1.4" + +"@lerna/child-process@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-5.1.4.tgz#164322198c27698fde9d913da120afa6501e390e" + integrity sha512-F7xP+bEdkE3JTyKz0t33QA5v2meXZrQQ0JmHa7/AlEg6D2r7gQ8UHSHuSUiNfX4drjpePe/9XaZylj01KLcx/w== + dependencies: + chalk "^4.1.0" + execa "^5.0.0" + strong-log-transformer "^2.1.0" + +"@lerna/clean@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-5.1.4.tgz#293d3669aed70d541a5e16c372144ef0e5ea3bfd" + integrity sha512-4Du/r8iYSYFpo1t5J1BYivmj84n9mGebt89isVsyqMmrCqd5B2ix/Z8PYPQFMwm7k9YYbV+sZGSpRvtXkn8kIw== + dependencies: + "@lerna/command" "5.1.4" + "@lerna/filter-options" "5.1.4" + "@lerna/prompt" "5.1.4" + "@lerna/pulse-till-done" "5.1.4" + "@lerna/rimraf-dir" "5.1.4" + p-map "^4.0.0" + p-map-series "^2.1.0" + p-waterfall "^2.1.1" + +"@lerna/cli@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/cli/-/cli-5.1.4.tgz#8cf9ba07c8d0d072d5aa61e55d4d3a16ab181d79" + integrity sha512-ckLSNJBY4iVmu6nBhHb8UchpWGm49z9pjsAEJQ4F/VNkT6zKsmOCfv2ahkvudQ77gc0K/dH+MTvoOHsH85bpow== + dependencies: + "@lerna/global-options" "5.1.4" + dedent "^0.7.0" + npmlog "^6.0.2" + yargs "^16.2.0" + +"@lerna/collect-uncommitted@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/collect-uncommitted/-/collect-uncommitted-5.1.4.tgz#19ddf64bdb9a630ed4a651b364efeee32416e307" + integrity sha512-CI9PXYQuewqA4ZBMRycDUSVRJmAxUeP8HEZ3aKNvAwlLxLlGCueh8qOHXZHxgkmF6eQtcEjblsReiDt8bFJZpA== + dependencies: + "@lerna/child-process" "5.1.4" + chalk "^4.1.0" + npmlog "^6.0.2" + +"@lerna/collect-updates@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-5.1.4.tgz#c03aef26c6b28d55a693b47b60fa3f4bebbee6cf" + integrity sha512-P1zlaZ0QkKIjbU3o7hjd4zcxzti1ndS4+eQNmlxZP3IcmlJ4+Ne+VxGeaACsjzPPBqSBWX1xcyMFLALH/Jo2CA== + dependencies: + "@lerna/child-process" "5.1.4" + "@lerna/describe-ref" "5.1.4" + minimatch "^3.0.4" + npmlog "^6.0.2" + slash "^3.0.0" + +"@lerna/command@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/command/-/command-5.1.4.tgz#c8f4b7f4700a6718310bea48e7ad2454e4659a80" + integrity sha512-S/3oIagN9/ntuGtljSxHu4liB9e9YFWsq/xZOR8YoqROJENv5G5zyAmHjXq90AR/tGmLvufzFliBfEIG9CywFA== + dependencies: + "@lerna/child-process" "5.1.4" + "@lerna/package-graph" "5.1.4" + "@lerna/project" "5.1.4" + "@lerna/validation-error" "5.1.4" + "@lerna/write-log-file" "5.1.4" + clone-deep "^4.0.1" + dedent "^0.7.0" + execa "^5.0.0" + is-ci "^2.0.0" + npmlog "^6.0.2" + +"@lerna/conventional-commits@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-5.1.4.tgz#9aa42c32fd7b306dfb4d838e18e02261635a496f" + integrity sha512-0v0exYOH9cJTNpKggqAw7vHVLlPjqO6Y20PUg44F3GOEjd54VIGDqu+MkVhflqvUftzZjmcUHDUGHVP+8dFBNw== + dependencies: + "@lerna/validation-error" "5.1.4" + conventional-changelog-angular "^5.0.12" + conventional-changelog-core "^4.2.2" + conventional-recommended-bump "^6.1.0" + fs-extra "^9.1.0" + get-stream "^6.0.0" + npm-package-arg "^8.1.0" + npmlog "^6.0.2" + pify "^5.0.0" + semver "^7.3.4" + +"@lerna/create-symlink@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/create-symlink/-/create-symlink-5.1.4.tgz#dc5f45f4e5192850eea920982c2f4e1808b899c5" + integrity sha512-VTTuCgM5gXk0frAFxfVQqfX9QxXKz6TKpKsHcC39BAR3aiSUW8vqRImbLvaFtKpnEMW0HshDfuzp6rRkaiyWYw== + dependencies: + cmd-shim "^4.1.0" + fs-extra "^9.1.0" + npmlog "^6.0.2" + +"@lerna/create@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/create/-/create-5.1.4.tgz#2829ff28c5e2b7060ee9e260868a8bf70f9d603e" + integrity sha512-UPR5EnFg0WzXiRIKl+MGHH3hBB6s1xkLDJNLGzac5Ztry/ibLDhl47wYoYcToiQ3/y3/3751WLJErF+A52mCyw== + dependencies: + "@lerna/child-process" "5.1.4" + "@lerna/command" "5.1.4" + "@lerna/npm-conf" "5.1.4" + "@lerna/validation-error" "5.1.4" + dedent "^0.7.0" + fs-extra "^9.1.0" + globby "^11.0.2" + init-package-json "^2.0.2" + npm-package-arg "^8.1.0" + p-reduce "^2.1.0" + pacote "^13.4.1" + pify "^5.0.0" + semver "^7.3.4" + slash "^3.0.0" + validate-npm-package-license "^3.0.4" + validate-npm-package-name "^3.0.0" + whatwg-url "^8.4.0" + yargs-parser "20.2.4" + +"@lerna/describe-ref@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-5.1.4.tgz#352da8757946ca1529a2655688323c9838a8e1ad" + integrity sha512-ztLWLIyrHPxVhs8yfVpCDIw2st5c246KfoTqjEX8N6s8v0dLs3vfCKCM70ej6lBNkwqBXSilgHrd3AkGq3kq6Q== + dependencies: + "@lerna/child-process" "5.1.4" + npmlog "^6.0.2" + +"@lerna/diff@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-5.1.4.tgz#43a3c39b84c3148a89dcee5a3b7434909c5e601a" + integrity sha512-o5chvMHcKQS4zkdGX7LCaMgNn0flrG9OEiGt8DCIzRUa6aWJAlE2oZyOj+VsiUxzaZJxm2oV+GkISQYRJPlPug== + dependencies: + "@lerna/child-process" "5.1.4" + "@lerna/command" "5.1.4" + "@lerna/validation-error" "5.1.4" + npmlog "^6.0.2" + +"@lerna/exec@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-5.1.4.tgz#b1b35c65593429dcc822fcbc72153d3cf3adf0b9" + integrity sha512-6vn1UCxJZTTt90WlWItI05yj4xaNOShgIl5Yi9mx1Ex6nVS32mmTOqHI/+Cn4M+P0C4u1hFymd2aIEfWnmdUsA== + dependencies: + "@lerna/child-process" "5.1.4" + "@lerna/command" "5.1.4" + "@lerna/filter-options" "5.1.4" + "@lerna/profiler" "5.1.4" + "@lerna/run-topologically" "5.1.4" + "@lerna/validation-error" "5.1.4" + p-map "^4.0.0" + +"@lerna/filter-options@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-5.1.4.tgz#417d9770cece09385c533ea89dbf140c9f49ded8" + integrity sha512-a6hLVZOb7awjI9Tk5hx90BB6GZz59npBRQN0kSG6drV1H+vi+wU7ee6OZ5EMHQgnzdZ6OjZQRHlWCCTXyNdKgQ== + dependencies: + "@lerna/collect-updates" "5.1.4" + "@lerna/filter-packages" "5.1.4" + dedent "^0.7.0" + npmlog "^6.0.2" + +"@lerna/filter-packages@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/filter-packages/-/filter-packages-5.1.4.tgz#54cd19d6f1fcbd27f0fd78c6f0c56412ebd7f454" + integrity sha512-a+ThrgYyGrTfBZUMfi/WvcqX3Ce6JaMZjTYoNAmKpHYNZFRqdmgOT1fFLLF+/y62XGqCf0wo50xRYNg0hIAf3Q== + dependencies: + "@lerna/validation-error" "5.1.4" + multimatch "^5.0.0" + npmlog "^6.0.2" + +"@lerna/get-npm-exec-opts@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-5.1.4.tgz#99a8f9ce73acac6206331b4cb87f24006e712cb4" + integrity sha512-A+cNgTWWQOcNGWz9wj40/NWK46v8TtTAmXuEPfzDruv6VdmXEVIuq7SCeUPj9+aRxMQXVCil0/Vyo2z6R9TDLw== + dependencies: + npmlog "^6.0.2" + +"@lerna/get-packed@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/get-packed/-/get-packed-5.1.4.tgz#cd376e017afb5327391ecdf781377c9b9fed09bb" + integrity sha512-JD9U4Sp7Dpt3nUdXAo5f9SIXK2QsBaguChCZ8VTAl3eb7j0o7nrHYoh1eAa8rDT2L9+AxcUFDMi/wDdCotlJmA== + dependencies: + fs-extra "^9.1.0" + ssri "^8.0.1" + tar "^6.1.0" + +"@lerna/github-client@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-5.1.4.tgz#818b921b12f2777dc55d1f4f955669c72fec7dcb" + integrity sha512-VAaH9ycnGVsaGWM5uRKvd0oXlOERHOEOwxXLaCnR1mA7k5490B5jTlwhSWYdA4s40CF9AOdIVNgBhP+T7MlcPw== + dependencies: + "@lerna/child-process" "5.1.4" + "@octokit/plugin-enterprise-rest" "^6.0.1" + "@octokit/rest" "^18.1.0" + git-url-parse "^11.4.4" + npmlog "^6.0.2" + +"@lerna/gitlab-client@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/gitlab-client/-/gitlab-client-5.1.4.tgz#82fb901eeaf75c51afa2c165e2bd26ef9695275c" + integrity sha512-F0Pa6Cv6TE0gbhuHR2gVVwdzstqePMZhTNcVY5So3YJrb1ppuUH/4cVXhRcEOj16QuWJ6yysxb7mj8tY4Zv0Bw== + dependencies: + node-fetch "^2.6.1" + npmlog "^6.0.2" + whatwg-url "^8.4.0" + +"@lerna/global-options@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/global-options/-/global-options-5.1.4.tgz#6daa9572c76171cdeb4cd4e48a7a1beae3e2ab4f" + integrity sha512-gs6y97tomIuyYdDr9uKQ5B5AR9m6wVft6lrxWlGlLo0prz39tx7fJ9wT2IpJ9iALCadkQW6g7XFtddwfm5VRhg== + +"@lerna/has-npm-version@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-5.1.4.tgz#4e2c01072ac4420e5ed3b645fa04d0673de38a25" + integrity sha512-U81b1nvqwF8PGyHib8/AWeGbaNipGdqXZsRO5g3ob9A5X57GXJ86cQVLejLi+znY4SmQcHladC4TotJkpNF1Ag== + dependencies: + "@lerna/child-process" "5.1.4" + semver "^7.3.4" + +"@lerna/import@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/import/-/import-5.1.4.tgz#28f5521f0fd69d894bb97325fa8c950d8200de3d" + integrity sha512-Kswe1NKJDUDlO/gbkFcurzaYlaj/fXlapHTaih9LmQDiVPOE9GphD5qnABCV0c4CqeSnCzRujT5BUjjL5z7viA== + dependencies: + "@lerna/child-process" "5.1.4" + "@lerna/command" "5.1.4" + "@lerna/prompt" "5.1.4" + "@lerna/pulse-till-done" "5.1.4" + "@lerna/validation-error" "5.1.4" + dedent "^0.7.0" + fs-extra "^9.1.0" + p-map-series "^2.1.0" + +"@lerna/info@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/info/-/info-5.1.4.tgz#fe803801d4ae747c25f914f7d3562d05902ede7e" + integrity sha512-9OMdNtmDMKLwfX+aZk9nHLfksYXuU7IcIiVJ9dR7gYx1PoKjXvTpd/+hd7t/tmElM21kmPVxQBu02L3KmXw+hQ== + dependencies: + "@lerna/command" "5.1.4" + "@lerna/output" "5.1.4" + envinfo "^7.7.4" + +"@lerna/init@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/init/-/init-5.1.4.tgz#7f195046e010c566f22d7a81b4167c0df2b88ef9" + integrity sha512-OdI5iWYT1JcB6f5mjmCjgpkOrpDdSSDzmSi34kp/NP1FkbskDoMffVBTQiV8/h6zAg3jk1+aLQYLMuR5E6nIwA== + dependencies: + "@lerna/child-process" "5.1.4" + "@lerna/command" "5.1.4" + fs-extra "^9.1.0" + p-map "^4.0.0" + write-json-file "^4.3.0" + +"@lerna/link@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/link/-/link-5.1.4.tgz#5009da73e9f7d899bb5495e9ebb2f1ececb83f4b" + integrity sha512-j73MW+vam6e8XdwyQGeHR9X7TUmgvLG0wV1vDLjSyrhk/Q5oFo0RTRgfDJqR4tCtRnv0vujvw5oDXfSbBmg67g== + dependencies: + "@lerna/command" "5.1.4" + "@lerna/package-graph" "5.1.4" + "@lerna/symlink-dependencies" "5.1.4" + p-map "^4.0.0" + slash "^3.0.0" + +"@lerna/list@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/list/-/list-5.1.4.tgz#19f8d229e3d39384e7f7ae4d67c7f3a3b8af751a" + integrity sha512-D7FAUik18s5FtHnBoPzodR8LUvH5b0a/ziV8ICaKWZ98H4w9qpNsQtBe0O+7DwUuqLKYpycst5tY5WVGnNwuNA== + dependencies: + "@lerna/command" "5.1.4" + "@lerna/filter-options" "5.1.4" + "@lerna/listable" "5.1.4" + "@lerna/output" "5.1.4" + +"@lerna/listable@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/listable/-/listable-5.1.4.tgz#0eb7d124502bfb3641959b6f86775e9ec9747e72" + integrity sha512-grGLrffBNX38l5mzZgkv4xE9UcAAKBi1s+LgloI3rusgTdE/B8gvCOYMqLf9V08iojs7Ke2xPf0whJmbEeK/qA== + dependencies: + "@lerna/query-graph" "5.1.4" + chalk "^4.1.0" + columnify "^1.6.0" + +"@lerna/log-packed@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/log-packed/-/log-packed-5.1.4.tgz#23f64fe9e80157af6e1e848d06bdb9d786439269" + integrity sha512-qJlWMVjc/uM1I7AWqrOPeBLVZy9YExi/QqUyvmkb8mmsPXnW7rxIJQdYgRifS5aFNTbX/MtG8Q65Rr4syiVnSA== + dependencies: + byte-size "^7.0.0" + columnify "^1.6.0" + has-unicode "^2.0.1" + npmlog "^6.0.2" + +"@lerna/npm-conf@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/npm-conf/-/npm-conf-5.1.4.tgz#6d746452806397e941399e6bf13da62e5c41cb8f" + integrity sha512-kNbw2jO0HD9P4+nS8RIFub549BiQYG/sdFUuNWu7cCjErB+g/5ayfE6Mn5HyiRPMYXVw73iR8IzvkCCDWEOB7Q== + dependencies: + config-chain "^1.1.12" + pify "^5.0.0" + +"@lerna/npm-dist-tag@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/npm-dist-tag/-/npm-dist-tag-5.1.4.tgz#32d6eb915134a5c26a62eea509b553aafb604b7b" + integrity sha512-9q5N3iy8KGFBsyRBmNEftj8ACeCXNh2JUBqk/wYGiB0WH0oVf0UY/uo6VUy8dZjyJ9Q0eZa1ONtFHIg3QrzGDA== + dependencies: + "@lerna/otplease" "5.1.4" + npm-package-arg "^8.1.0" + npm-registry-fetch "^9.0.0" + npmlog "^6.0.2" + +"@lerna/npm-install@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-5.1.4.tgz#5b85d0b097380ae089297cb9903508230df5dd49" + integrity sha512-DbbUK2Zy7ZBpkHimlFKf7XbGzBsoPfqzf0i9hIYBHmND9YWSgIgVFJcyRH7E6UKpr4wRChW4h6xEV81jKykB7w== + dependencies: + "@lerna/child-process" "5.1.4" + "@lerna/get-npm-exec-opts" "5.1.4" + fs-extra "^9.1.0" + npm-package-arg "^8.1.0" + npmlog "^6.0.2" + signal-exit "^3.0.3" + write-pkg "^4.0.0" + +"@lerna/npm-publish@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/npm-publish/-/npm-publish-5.1.4.tgz#7f922a7e778b94106ba8929c30324554238faaab" + integrity sha512-MXtd2cFN+oJMxj9m1fXYAo+KE2BzO84Ukt3uAhQb1cXU01ZCwqGl/lQRWw5vI88emrKs0akx3d6E77PFpX9rpw== + dependencies: + "@lerna/otplease" "5.1.4" + "@lerna/run-lifecycle" "5.1.4" + fs-extra "^9.1.0" + libnpmpublish "^4.0.0" + npm-package-arg "^8.1.0" + npmlog "^6.0.2" + pify "^5.0.0" + read-package-json "^3.0.0" + +"@lerna/npm-run-script@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-5.1.4.tgz#6db6a15ff4a012d064b14c72601031b64c8137ec" + integrity sha512-vw2G69lFmFzdX553GidE66QgCZ3cGyxoOvnpCdvZ1n9AS5ZwZSiL8Ms6N3Vj+AOhESFZmFZkzIVhtpX5/xNzLg== + dependencies: + "@lerna/child-process" "5.1.4" + "@lerna/get-npm-exec-opts" "5.1.4" + npmlog "^6.0.2" + +"@lerna/otplease@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/otplease/-/otplease-5.1.4.tgz#211956a78fa9ff2bed94e3b2a32762b68dfbd15f" + integrity sha512-t3qKC55D7rCacNTsqQwn25XxDRQXgRHYWS0gqn2ch+dTwXOI61Uto9okVhgn2ZfZVydJ3sjnktOsPeSXhQRQew== + dependencies: + "@lerna/prompt" "5.1.4" + +"@lerna/output@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/output/-/output-5.1.4.tgz#6b56336b612a573994125a11a7b139deda2b5576" + integrity sha512-E9nLEcV5GJbTKJd/d+cvU54CIzQqoU2rJAeXeyHTufbjgCTPk4I8uDNHmG7uJ+aPrif6PPBt1IIw+w5UnStfdw== + dependencies: + npmlog "^6.0.2" + +"@lerna/pack-directory@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/pack-directory/-/pack-directory-5.1.4.tgz#9a069e5bfc0d61391bd6c6f2ba362c8907cb80a7" + integrity sha512-TsltQrbwC/bPwQbL5i7WCMNM4Chl8+iqzawRZbILfjYpt3UK9xSV2tWfc9QtbmRBETvcFz/UMKQQDz+LMWN9jw== + dependencies: + "@lerna/get-packed" "5.1.4" + "@lerna/package" "5.1.4" + "@lerna/run-lifecycle" "5.1.4" + "@lerna/temp-write" "5.1.4" + npm-packlist "^2.1.4" + npmlog "^6.0.2" + tar "^6.1.0" + +"@lerna/package-graph@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/package-graph/-/package-graph-5.1.4.tgz#df5910f588334284637a6a3cc24766806ba88f52" + integrity sha512-dP1gLcrqou5/8zef7u5ne4GTslNXULjpi3dDiljohKNR4XelsC4lkkF9m1Uzn9E1nAphHRhWXrRq40kqxmdYXg== + dependencies: + "@lerna/prerelease-id-from-version" "5.1.4" + "@lerna/validation-error" "5.1.4" + npm-package-arg "^8.1.0" + npmlog "^6.0.2" + semver "^7.3.4" + +"@lerna/package@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/package/-/package-5.1.4.tgz#7dd77d18cd0227793afa9819be988167bcc0cb02" + integrity sha512-L0zsxslJZ+swkG/KLU3TQHmWPR0hf0eLIdOROyA9Nxvuo8C/702ddYZcuEYcz9t/jOuSgSB2s90iK2oTIncNbw== + dependencies: + load-json-file "^6.2.0" + npm-package-arg "^8.1.0" + write-pkg "^4.0.0" + +"@lerna/prerelease-id-from-version@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-5.1.4.tgz#ea34adb5810622a656fa8dea17595d6b794e2872" + integrity sha512-kDcXKKFD6Ww/FinLEvsY1P3aIiuVLyonkttvfKJTJvm3ymz7/fBKz8GotFXuONVC1xSIK9Nrk3jGYs6ZGoha+w== + dependencies: + semver "^7.3.4" + +"@lerna/profiler@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/profiler/-/profiler-5.1.4.tgz#ce679f1a0b29489e3b530c6708c6f951d46fd15d" + integrity sha512-JLkS90+CSmi85v3SlJc5Wjk73MHmIviqtL3fM/Z6clBLbsRPkbBBfSwXKp7O281knF6E2UNTrWOtEG7b6wG3TQ== + dependencies: + fs-extra "^9.1.0" + npmlog "^6.0.2" + upath "^2.0.1" + +"@lerna/project@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/project/-/project-5.1.4.tgz#5185a6ef17d4cc2be0fec505e0f4507c2e68af3b" + integrity sha512-k0z3w45t746uAUkN+jY/jF+/BqHodGFYaUfM0DTDOGUWC8tXzxuqk3bchShp6Wct2gwNQWbtWHl50Jhhw5PC5g== + dependencies: + "@lerna/package" "5.1.4" + "@lerna/validation-error" "5.1.4" + cosmiconfig "^7.0.0" + dedent "^0.7.0" + dot-prop "^6.0.1" + glob-parent "^5.1.1" + globby "^11.0.2" + load-json-file "^6.2.0" + npmlog "^6.0.2" + p-map "^4.0.0" + resolve-from "^5.0.0" + write-json-file "^4.3.0" + +"@lerna/prompt@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/prompt/-/prompt-5.1.4.tgz#16f6f56752e0e542350cafbc8583d3d4690d8394" + integrity sha512-AiE8NIzh+x2+F0t96M+rfwLtKzBNXjQEWXtBfEcA1eRqanMWUr6ejfmdkoEzXVrMzyY/ugPdWQYbGCI00iF7Tg== + dependencies: + inquirer "^7.3.3" + npmlog "^6.0.2" + +"@lerna/publish@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-5.1.4.tgz#d9d7203ec7e9dc3d0a9b8123a2c0b1afa536bd05" + integrity sha512-hbFAwOlyUR4AUBd7qTQXXVKgaxTS4Mz4Kkjxz8g7jtqo+T0KvU3JbfwDqxOiCwcDk+qkrBbkwbvc27jcObSwkw== + dependencies: + "@lerna/check-working-tree" "5.1.4" + "@lerna/child-process" "5.1.4" + "@lerna/collect-updates" "5.1.4" + "@lerna/command" "5.1.4" + "@lerna/describe-ref" "5.1.4" + "@lerna/log-packed" "5.1.4" + "@lerna/npm-conf" "5.1.4" + "@lerna/npm-dist-tag" "5.1.4" + "@lerna/npm-publish" "5.1.4" + "@lerna/otplease" "5.1.4" + "@lerna/output" "5.1.4" + "@lerna/pack-directory" "5.1.4" + "@lerna/prerelease-id-from-version" "5.1.4" + "@lerna/prompt" "5.1.4" + "@lerna/pulse-till-done" "5.1.4" + "@lerna/run-lifecycle" "5.1.4" + "@lerna/run-topologically" "5.1.4" + "@lerna/validation-error" "5.1.4" + "@lerna/version" "5.1.4" + fs-extra "^9.1.0" + libnpmaccess "^4.0.1" + npm-package-arg "^8.1.0" + npm-registry-fetch "^9.0.0" + npmlog "^6.0.2" + p-map "^4.0.0" + p-pipe "^3.1.0" + pacote "^13.4.1" + semver "^7.3.4" + +"@lerna/pulse-till-done@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/pulse-till-done/-/pulse-till-done-5.1.4.tgz#b82b362341665e44e2885abd84b22e4122bafcaa" + integrity sha512-zFPzv6cY0OcqtcR91ueZqd+ulTLE4vPk9l6iPAfefgqh6w0E6hSmG6J9RmYE3gaMHSFJdvYHb/yyTPLF32J9lg== + dependencies: + npmlog "^6.0.2" + +"@lerna/query-graph@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/query-graph/-/query-graph-5.1.4.tgz#7333bebc711e03755fb96e23b4e99c974eac3d10" + integrity sha512-G8DYNqp5ISbbMjEJhGst1GHk59zO18IG9oaVSK14M7iF3qCLtg0iJ1Do4LDNpda3EF8PrLOx2mrNM5MBcGMjEg== + dependencies: + "@lerna/package-graph" "5.1.4" + +"@lerna/resolve-symlink@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/resolve-symlink/-/resolve-symlink-5.1.4.tgz#7173d4103d1ae868a000336a636fcbfd15a8ae53" + integrity sha512-hpnaX5tznAtbQXlyc92kJiywdTnnbCf6wihSZwDiVnVgXuHJ3LvmjN677h9A0jobY6KdTT+wIoAHpJuZHj60vQ== + dependencies: + fs-extra "^9.1.0" + npmlog "^6.0.2" + read-cmd-shim "^2.0.0" + +"@lerna/rimraf-dir@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-5.1.4.tgz#2d216d97d223aa7a521ae77cae1ae29a6a0d96ca" + integrity sha512-WvHm4gE1/HWbI4gCjJw3clPT+FRq2Ob9I9EDbfw4c307MNT4kW4bJU2mt0nyv/uwYhUkTG+GQVrlt+Dtcif77g== + dependencies: + "@lerna/child-process" "5.1.4" + npmlog "^6.0.2" + path-exists "^4.0.0" + rimraf "^3.0.2" + +"@lerna/run-lifecycle@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/run-lifecycle/-/run-lifecycle-5.1.4.tgz#9d0e80bec6dee25342640b7ccfbe13d89d22e32d" + integrity sha512-ubmqi1ixebBHSTYS0oK8MoqBoJE7UDrXWTWsv84UrXiPutTffLR8ZQJKlMEcetQVzX9qbjpKbzc+jQWXPWid2A== + dependencies: + "@lerna/npm-conf" "5.1.4" + "@npmcli/run-script" "^3.0.2" + npmlog "^6.0.2" + +"@lerna/run-topologically@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/run-topologically/-/run-topologically-5.1.4.tgz#f026407253e751102f2dba9ee764daa4b3a88bcf" + integrity sha512-MckWfLu/xuRtaThdUgrJC2naumv2LOIiMoJfxCdYpiCrIgq5YrwqOxjQ0awHqQhkvFZ5G91ucBcBEIMsOou1iw== + dependencies: + "@lerna/query-graph" "5.1.4" + p-queue "^6.6.2" + +"@lerna/run@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/run/-/run-5.1.4.tgz#fb97a659eee440f978231306a0d8745a9a6eb39f" + integrity sha512-iaTioOF66z02Y9ml/Ba0ePpXOwZ+BkODcNXrJbyW8WhraL0fSjyno0FspO1Eu0nG4JMtgCsoEzHNphsk7Wg+7A== + dependencies: + "@lerna/command" "5.1.4" + "@lerna/filter-options" "5.1.4" + "@lerna/npm-run-script" "5.1.4" + "@lerna/output" "5.1.4" + "@lerna/profiler" "5.1.4" + "@lerna/run-topologically" "5.1.4" + "@lerna/timer" "5.1.4" + "@lerna/validation-error" "5.1.4" + p-map "^4.0.0" + +"@lerna/symlink-binary@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-5.1.4.tgz#1bba4aa23125c8a8ce3d6b15b869caa62824c6b5" + integrity sha512-SNjHxCNTCD0Xfj3CNBTG+3ut4aDAVaq+SrB2ckFNmZ5Z9yFdnX6aP+PBzLD/0q5hj18lGlaJ8iZjD/ubbrgFCA== + dependencies: + "@lerna/create-symlink" "5.1.4" + "@lerna/package" "5.1.4" + fs-extra "^9.1.0" + p-map "^4.0.0" + +"@lerna/symlink-dependencies@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-5.1.4.tgz#70497b85cde43e9add4eacb10f1de35b97f937b9" + integrity sha512-SuzylyNs1R5bVRqSCwfbQLdDP83RX8ncQxOy2SSSrScwkzdBCDqDPh4haeADsq2+RoOQBItn1PDfzUCNAWomDA== + dependencies: + "@lerna/create-symlink" "5.1.4" + "@lerna/resolve-symlink" "5.1.4" + "@lerna/symlink-binary" "5.1.4" + fs-extra "^9.1.0" + p-map "^4.0.0" + p-map-series "^2.1.0" + +"@lerna/temp-write@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/temp-write/-/temp-write-5.1.4.tgz#eca417418496fbb29d8bae36b548fd7afb46523f" + integrity sha512-f+6+ud87pyitM9zAq7GBhB7uoHTcgLJvR3YGv5sNja4jIl3+zdKPDcyxzVyQb38knuRSkGM8NjYOWi4zwcMaGw== + dependencies: + graceful-fs "^4.1.15" + is-stream "^2.0.0" + make-dir "^3.0.0" + temp-dir "^1.0.0" + uuid "^8.3.2" + +"@lerna/timer@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/timer/-/timer-5.1.4.tgz#a94734ba5bed1f3ad9b6fc0ce9609e5e624bc7ec" + integrity sha512-fhQtqkLxNexPWzhI1WAxZnHIBM8VhChvUJu503u1Rmp2JxhXbTE4Txnu1gPvqlDjdoE6ck0vN5icmfMVRwKc8g== + +"@lerna/validation-error@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/validation-error/-/validation-error-5.1.4.tgz#095d12367ec17a5fc00c0cf6cd8a83c3ddb9a9c3" + integrity sha512-wys9Fv/bUy7sYXOK9t+V3XSyEHK5tUXwY22nfIDYu416WcSkkE4DI8Q2nTv4nYYOmG2Y7IOhaSenbsPLQ0VqtQ== + dependencies: + npmlog "^6.0.2" + +"@lerna/version@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/version/-/version-5.1.4.tgz#87a2c19ba0d1e8c8417d6c2b87d1702e95a0bdf5" + integrity sha512-cYgm1SNdiK129JoWI8WMwjsxaIyeAC1gCaToWk36Tw+BCF3PbkdoTKdneDmJ+7qbX1QrzxsgHTcjwIt4lZPEqQ== + dependencies: + "@lerna/check-working-tree" "5.1.4" + "@lerna/child-process" "5.1.4" + "@lerna/collect-updates" "5.1.4" + "@lerna/command" "5.1.4" + "@lerna/conventional-commits" "5.1.4" + "@lerna/github-client" "5.1.4" + "@lerna/gitlab-client" "5.1.4" + "@lerna/output" "5.1.4" + "@lerna/prerelease-id-from-version" "5.1.4" + "@lerna/prompt" "5.1.4" + "@lerna/run-lifecycle" "5.1.4" + "@lerna/run-topologically" "5.1.4" + "@lerna/temp-write" "5.1.4" + "@lerna/validation-error" "5.1.4" + chalk "^4.1.0" + dedent "^0.7.0" + load-json-file "^6.2.0" + minimatch "^3.0.4" + npmlog "^6.0.2" + p-map "^4.0.0" + p-pipe "^3.1.0" + p-reduce "^2.1.0" + p-waterfall "^2.1.1" + semver "^7.3.4" + slash "^3.0.0" + write-json-file "^4.3.0" + +"@lerna/write-log-file@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@lerna/write-log-file/-/write-log-file-5.1.4.tgz#bd9cc578d0a35be11f0c489252e945478a0d3c0d" + integrity sha512-ISJbkjaSKhJ4d7V90RFvuwDQFq9ZH/KN475KFJr+TBFZTwMiXuBahlq+j8/a+nItejNnuPD4/xlWuzCOuGJORQ== + dependencies: + npmlog "^6.0.2" + write-file-atomic "^3.0.3" + +"@mapbox/node-pre-gyp@^1.0.0": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz#09a8781a3a036151cdebbe8719d6f8b25d4058bc" + integrity sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + +"@nestjs/axios@^0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@nestjs/axios/-/axios-0.0.8.tgz#4e321e36b6bc3ab0f02d80bafe04ae39edfdcacf" + integrity sha512-oJyfR9/h9tVk776il0829xyj3b2e81yTu6HjPraxynwNtMNGqZBHHmAQL24yMB3tVbBM0RvG3eUXH8+pRCGwlg== + dependencies: + axios "0.27.2" + +"@nestjs/cli@^8.2.6": + version "8.2.6" + resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-8.2.6.tgz#915aef9c1c025280ab8ebc915530fa0090bcbe49" + integrity sha512-uvwKbUZJmgdJu1D24e+uUqHnwoB/0R9hLfUJjr5pTvLlP/RJugHAdJr7m1dQe92Xzdyi36kBN4Id3RXHgfz1UA== + dependencies: + "@angular-devkit/core" "13.3.5" + "@angular-devkit/schematics" "13.3.5" + "@angular-devkit/schematics-cli" "13.3.5" + "@nestjs/schematics" "^8.0.3" + chalk "3.0.0" + chokidar "3.5.3" + cli-table3 "0.6.2" + commander "4.1.1" + fork-ts-checker-webpack-plugin "7.2.11" + inquirer "7.3.3" + node-emoji "1.11.0" + ora "5.4.1" + os-name "4.0.1" + rimraf "3.0.2" + shelljs "0.8.5" + source-map-support "0.5.21" + tree-kill "1.2.2" + tsconfig-paths "3.14.1" + tsconfig-paths-webpack-plugin "3.5.2" + typescript "4.6.4" + webpack "5.72.1" + webpack-node-externals "3.0.0" + +"@nestjs/common@^8.4.7": + version "8.4.7" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-8.4.7.tgz#fc4a575b797e230bb5a0bcab6da8b796aa88d605" + integrity sha512-m/YsbcBal+gA5CFrDpqXqsSfylo+DIQrkFY3qhVIltsYRfu8ct8J9pqsTO6OPf3mvqdOpFGrV5sBjoyAzOBvsw== + dependencies: + axios "0.27.2" + iterare "1.2.1" + tslib "2.4.0" + uuid "8.3.2" + +"@nestjs/config@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-2.1.0.tgz#334d6c3211f288eb40b1f8e2dbfbaf50b538f300" + integrity sha512-wUpt1/QJEN7xnJl4pM3c9rHrY1widq2yPOZfjaMD1//XAP9LLHTaW+RxSEG6BSGIm3w4wGtjco+gKNB2WL7yRg== + dependencies: + dotenv "16.0.1" + dotenv-expand "8.0.3" + lodash "4.17.21" + uuid "8.3.2" + +"@nestjs/core@^8.4.7": + version "8.4.7" + resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-8.4.7.tgz#fbec7fa744ac8749a4b966f759a6656c1cf43883" + integrity sha512-XB9uexHqzr2xkPo6QSiQWJJttyYYLmvQ5My64cFvWFi7Wk2NIus0/xUNInwX3kmFWB6pF1ab5Y2ZBvWdPwGBhw== + dependencies: + "@nuxtjs/opencollective" "0.3.2" + fast-safe-stringify "2.1.1" + iterare "1.2.1" + object-hash "3.0.0" + path-to-regexp "3.2.0" + tslib "2.4.0" + uuid "8.3.2" + +"@nestjs/jwt@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@nestjs/jwt/-/jwt-8.0.1.tgz#e00f6810705a75d5680903241f4e07272e59e6d5" + integrity sha512-9WGfgngX8aclC/MC+CH35Ooo4iPVKc+7xLXaBV6o4ty8g2uZdPomry7cSdK/e6Lv623O/84WapThnPoAtW/jvA== + dependencies: + "@types/jsonwebtoken" "8.5.8" + jsonwebtoken "8.5.1" + +"@nestjs/mongoose@^9.1.1": + version "9.1.1" + resolved "https://registry.yarnpkg.com/@nestjs/mongoose/-/mongoose-9.1.1.tgz#b7ef085a2ca0a04edbf2ffc18877d602f6ac891b" + integrity sha512-sKZKI+walquKbCu3wSgO9X6a8MAkfsOlUTdPGv6IFo9NtTSp4sdrfiWFk2rQVc1N05J4Jdt94fnPKWJf+7U8uQ== + +"@nestjs/passport@^8.2.2": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-8.2.2.tgz#32b3932b83740895f037eabaf812c44f5ec18b3a" + integrity sha512-Ytbn8j7WZ4INmEntOpdJY1isTgdQqZkx5ADz8zsZ5wAp0t8tc5GF/A+GlXlmn9/yRPwZHSbmHpv7Qt2EIiNnrw== + +"@nestjs/platform-express@^8.4.7": + version "8.4.7" + resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-8.4.7.tgz#402a3d3c47327a164bb3867615f423c29d1a6cd9" + integrity sha512-lPE5Ltg2NbQGRQIwXWY+4cNrXhJdycbxFDQ8mNxSIuv+LbrJBIdEB/NONk+LLn9N/8d2+I2LsIETGQrPvsejBg== + dependencies: + body-parser "1.20.0" + cors "2.8.5" + express "4.18.1" + multer "1.4.4-lts.1" + tslib "2.4.0" + +"@nestjs/schedule@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@nestjs/schedule/-/schedule-2.0.1.tgz#0f047e605c5c93fbc12be429b4d73dc322e58628" + integrity sha512-NqiCk3P7HDMw55kpefNIzAAQEsP+6dDIXUt4/KQANtAZ+opdLzo8rkzI0j8vDqgYeTh+PKq+V6zwSRjR61xPAQ== + dependencies: + cron "2.0.0" + uuid "8.3.2" + +"@nestjs/schematics@^8.0.11", "@nestjs/schematics@^8.0.3": + version "8.0.11" + resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-8.0.11.tgz#5d0c56184826660a2c01b1c326dbdbb12880e864" + integrity sha512-W/WzaxgH5aE01AiIErE9QrQJ73VR/M/8p8pq0LZmjmNcjZqU5kQyOWUxZg13WYfSpJdOa62t6TZRtFDmgZPoIg== + dependencies: + "@angular-devkit/core" "13.3.5" + "@angular-devkit/schematics" "13.3.5" + fs-extra "10.1.0" + jsonc-parser "3.0.0" + pluralize "8.0.0" + +"@nestjs/terminus@^8.0.8": + version "8.0.8" + resolved "https://registry.yarnpkg.com/@nestjs/terminus/-/terminus-8.0.8.tgz#d8303201e4cde8e1e48f500d8a4f3b7f4449788f" + integrity sha512-VQwIfnbnvqVN1K2o82mjbqnFJnwP/TT8qojSsIUIEqW29elz0TfvjquICrQdEfqxsqn6DcGjLYF4kjHBORsJZQ== + dependencies: + check-disk-space "3.3.0" + +"@nestjs/testing@^8.4.6", "@nestjs/testing@^8.4.7": + version "8.4.7" + resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-8.4.7.tgz#fe4f356c0e081e25fe8c899a65e91dd88947fd13" + integrity sha512-aedpeJFicTBeiTCvJWUG45WMMS53f5eu8t2fXsfjsU1t+WdDJqYcZyrlCzA4dL1B7MfbqaTURdvuVVHTmJO8ag== + dependencies: + tslib "2.4.0" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@npmcli/arborist@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-5.2.0.tgz#ee40dfe1f81ae1524819ee39c8f3e7022b0d6269" + integrity sha512-zWV7scFGL0SmpvfQyIWnMFbU/0YgtMNyvJiJwR98kyjUSntJGWFFR0O600d5W+TrDcTg0GyDbY+HdzGEg+GXLg== + dependencies: + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/installed-package-contents" "^1.0.7" + "@npmcli/map-workspaces" "^2.0.3" + "@npmcli/metavuln-calculator" "^3.0.1" + "@npmcli/move-file" "^2.0.0" + "@npmcli/name-from-folder" "^1.0.1" + "@npmcli/node-gyp" "^2.0.0" + "@npmcli/package-json" "^2.0.0" + "@npmcli/run-script" "^3.0.0" + bin-links "^3.0.0" + cacache "^16.0.6" + common-ancestor-path "^1.0.1" + json-parse-even-better-errors "^2.3.1" + json-stringify-nice "^1.1.4" + mkdirp "^1.0.4" + mkdirp-infer-owner "^2.0.0" + nopt "^5.0.0" + npm-install-checks "^5.0.0" + npm-package-arg "^9.0.0" + npm-pick-manifest "^7.0.0" + npm-registry-fetch "^13.0.0" + npmlog "^6.0.2" + pacote "^13.0.5" + parse-conflict-json "^2.0.1" + proc-log "^2.0.0" + promise-all-reject-late "^1.0.0" + promise-call-limit "^1.0.1" + read-package-json-fast "^2.0.2" + readdir-scoped-modules "^1.1.0" + rimraf "^3.0.2" + semver "^7.3.7" + ssri "^9.0.0" + treeverse "^2.0.0" + walk-up-path "^1.0.0" + +"@npmcli/ci-detect@^1.0.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@npmcli/ci-detect/-/ci-detect-1.4.0.tgz#18478bbaa900c37bfbd8a2006a6262c62e8b0fe1" + integrity sha512-3BGrt6FLjqM6br5AhWRKTr3u5GIVkjRYeAFrMp3HjnfICrg4xOrVRwFavKT6tsp++bq5dluL5t8ME/Nha/6c1Q== + +"@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/fs@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.0.tgz#f2a21c28386e299d1a9fae8051d35ad180e33109" + integrity sha512-DmfBvNXGaetMxj9LTp8NAN9vEidXURrf5ZTslQzEAi/6GbW+4yjaLFQc6Tue5cpZ9Frlk4OBo/Snf1Bh/S7qTQ== + dependencies: + "@gar/promisify" "^1.1.3" + semver "^7.3.5" + +"@npmcli/git@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-3.0.1.tgz#049b99b1381a2ddf7dc56ba3e91eaf76ca803a8d" + integrity sha512-UU85F/T+F1oVn3IsB/L6k9zXIMpXBuUBE25QDH0SsURwT6IOBqkC7M16uqo2vVZIyji3X1K4XH9luip7YekH1A== + dependencies: + "@npmcli/promise-spawn" "^3.0.0" + lru-cache "^7.4.4" + mkdirp "^1.0.4" + npm-pick-manifest "^7.0.0" + proc-log "^2.0.0" + promise-inflight "^1.0.1" + promise-retry "^2.0.1" + semver "^7.3.5" + which "^2.0.2" + +"@npmcli/installed-package-contents@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz#ab7408c6147911b970a8abe261ce512232a3f4fa" + integrity sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw== + dependencies: + npm-bundled "^1.1.1" + npm-normalize-package-bin "^1.0.1" + +"@npmcli/map-workspaces@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@npmcli/map-workspaces/-/map-workspaces-2.0.3.tgz#2d3c75119ee53246e9aa75bc469a55281cd5f08f" + integrity sha512-X6suAun5QyupNM8iHkNPh0AHdRC2rb1W+MTdMvvA/2ixgmqZwlq5cGUBgmKHUHT2LgrkKJMAXbfAoTxOigpK8Q== + dependencies: + "@npmcli/name-from-folder" "^1.0.1" + glob "^8.0.1" + minimatch "^5.0.1" + read-package-json-fast "^2.0.3" + +"@npmcli/metavuln-calculator@^3.0.1": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/metavuln-calculator/-/metavuln-calculator-3.1.0.tgz#b1c2f0991c4f2d992b1615a54d4358c05efc3702" + integrity sha512-Q5fbQqGDlYqk7kWrbg6E2j/mtqQjZop0ZE6735wYA1tYNHguIDjAuWs+kFb5rJCkLIlXllfapvsyotYKiZOTBA== + dependencies: + cacache "^16.0.0" + json-parse-even-better-errors "^2.3.1" + pacote "^13.0.3" + semver "^7.3.5" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@npmcli/move-file@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.0.tgz#417f585016081a0184cef3e38902cd917a9bbd02" + integrity sha512-UR6D5f4KEGWJV6BGPH3Qb2EtgH+t+1XQ1Tt85c7qicN6cezzuHPdZwwAxqZr4JLtnQu0LZsTza/5gmNmSl8XLg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@npmcli/name-from-folder@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz#77ecd0a4fcb772ba6fe927e2e2e155fbec2e6b1a" + integrity sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA== + +"@npmcli/node-gyp@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz#8c20e53e34e9078d18815c1d2dda6f2420d75e35" + integrity sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A== + +"@npmcli/package-json@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/package-json/-/package-json-2.0.0.tgz#3bbcf4677e21055adbe673d9f08c9f9cde942e4a" + integrity sha512-42jnZ6yl16GzjWSH7vtrmWyJDGVa/LXPdpN2rcUWolFjc9ON2N3uz0qdBbQACfmhuJZ2lbKYtmK5qx68ZPLHMA== + dependencies: + json-parse-even-better-errors "^2.3.1" + +"@npmcli/promise-spawn@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz#53283b5f18f855c6925f23c24e67c911501ef573" + integrity sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g== + dependencies: + infer-owner "^1.0.4" + +"@npmcli/run-script@^3.0.0", "@npmcli/run-script@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-3.0.3.tgz#66afa6e0c4c3484056195f295fa6c1d1a45ddf58" + integrity sha512-ZXL6qgC5NjwfZJ2nET+ZSLEz/PJgJ/5CU90C2S66dZY4Jw73DasS4ZCXuy/KHWYP0imjJ4VtA+Gebb5BxxKp9Q== + dependencies: + "@npmcli/node-gyp" "^2.0.0" + "@npmcli/promise-spawn" "^3.0.0" + node-gyp "^8.4.1" + read-package-json-fast "^2.0.3" + +"@npmcli/run-script@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-4.1.0.tgz#1ecd270f6a14841721848f0a7dba441676fc45ab" + integrity sha512-bVX9/2YhQscdlC5WEDQ8HH7bw32klCiAvOSvUHJcmeUTUuaQ7z42KiwmnkXWqhVKKhbWPBp+5H0kN6WDyfknzw== + dependencies: + "@npmcli/node-gyp" "^2.0.0" + "@npmcli/promise-spawn" "^3.0.0" + node-gyp "^9.0.0" + read-package-json-fast "^2.0.3" + +"@nrwl/cli@14.3.6": + version "14.3.6" + resolved "https://registry.yarnpkg.com/@nrwl/cli/-/cli-14.3.6.tgz#bf9d36822bbe9db42c250f4151df2b496ae7f13f" + integrity sha512-MNCBzM3ZDsujEc5crltH/kEyNBKx6DofLHdpzY1EoiY+yF57W7E1B6ERkVUGDc52sR4snEUKpQLXdVFjwP+z8A== + dependencies: + nx "14.3.6" + +"@nrwl/tao@14.3.6": + version "14.3.6" + resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-14.3.6.tgz#cb9ac487397dd1c399add2fd8564bc6debc0fec4" + integrity sha512-g3y6VRq4wNtbFZIclJwRR/1hcTesx6h64g7h80VWtyRw0pVq/0zAjb8abeQSTDLM15uc1pQJYPEfsR/KgI3Sow== + dependencies: + nx "14.3.6" + +"@nuxtjs/opencollective@0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz#620ce1044f7ac77185e825e1936115bb38e2681c" + integrity sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA== + dependencies: + chalk "^4.1.0" + consola "^2.15.0" + node-fetch "^2.6.1" + +"@octokit/auth-token@^2.4.4": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.5.0.tgz#27c37ea26c205f28443402477ffd261311f21e36" + integrity sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g== + dependencies: + "@octokit/types" "^6.0.3" + +"@octokit/core@^3.5.1": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.6.0.tgz#3376cb9f3008d9b3d110370d90e0a1fcd5fe6085" + integrity sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q== + dependencies: + "@octokit/auth-token" "^2.4.4" + "@octokit/graphql" "^4.5.8" + "@octokit/request" "^5.6.3" + "@octokit/request-error" "^2.0.5" + "@octokit/types" "^6.0.3" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + +"@octokit/endpoint@^6.0.1": + version "6.0.12" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" + integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== + dependencies: + "@octokit/types" "^6.0.3" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^4.5.8": + version "4.8.0" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.8.0.tgz#664d9b11c0e12112cbf78e10f49a05959aa22cc3" + integrity sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg== + dependencies: + "@octokit/request" "^5.6.0" + "@octokit/types" "^6.0.3" + universal-user-agent "^6.0.0" + +"@octokit/openapi-types@^12.4.0": + version "12.4.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-12.4.0.tgz#fd8bf5db72bd566c5ba2cb76754512a9ebe66e71" + integrity sha512-Npcb7Pv30b33U04jvcD7l75yLU0mxhuX2Xqrn51YyZ5WTkF04bpbxLaZ6GcaTqu03WZQHoO/Gbfp95NGRueDUA== + +"@octokit/plugin-enterprise-rest@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" + integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw== + +"@octokit/plugin-paginate-rest@^2.16.8": + version "2.19.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.19.0.tgz#b52eae6ecacfa1f5583dc2cc0985cfbed3ca78b0" + integrity sha512-hQ4Qysg2hNmEMuZeJkvyzM4eSZiTifOKqYAMsW8FnxFKowhuwWICSgBQ9Gn9GpUmgKB7qaf1hFvMjYaTAg5jQA== + dependencies: + "@octokit/types" "^6.36.0" + +"@octokit/plugin-request-log@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== + +"@octokit/plugin-rest-endpoint-methods@^5.12.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.15.0.tgz#6c8251b55c33315a6e53e5b55654f72023ed5049" + integrity sha512-Gsw9+Xm56jVhfbJoy4pt6eOOyf8/3K6CAnx1Sl7U2GhZWcg8MR6YgXWnpfdF69S2ViMXLA7nfvTDAsZpFlkLRw== + dependencies: + "@octokit/types" "^6.36.0" + deprecation "^2.3.1" + +"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" + integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== + dependencies: + "@octokit/types" "^6.0.3" + deprecation "^2.0.0" + once "^1.4.0" + +"@octokit/request@^5.6.0", "@octokit/request@^5.6.3": + version "5.6.3" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.3.tgz#19a022515a5bba965ac06c9d1334514eb50c48b0" + integrity sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A== + dependencies: + "@octokit/endpoint" "^6.0.1" + "@octokit/request-error" "^2.1.0" + "@octokit/types" "^6.16.1" + is-plain-object "^5.0.0" + node-fetch "^2.6.7" + universal-user-agent "^6.0.0" + +"@octokit/rest@^18.1.0": + version "18.12.0" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.12.0.tgz#f06bc4952fc87130308d810ca9d00e79f6988881" + integrity sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q== + dependencies: + "@octokit/core" "^3.5.1" + "@octokit/plugin-paginate-rest" "^2.16.8" + "@octokit/plugin-request-log" "^1.0.4" + "@octokit/plugin-rest-endpoint-methods" "^5.12.0" + +"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.36.0": + version "6.37.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.37.0.tgz#32eb78edb34cf5cea4ba5753ab8db75b776d617a" + integrity sha512-BXWQhFKRkjX4dVW5L2oYa0hzWOAqsEsujXsQLSdepPoDZfYdubrD1KDGpyNldGXtR8QM/WezDcxcIN1UKJMGPA== + dependencies: + "@octokit/openapi-types" "^12.4.0" + +"@parcel/watcher@2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.4.tgz#f300fef4cc38008ff4b8c29d92588eced3ce014b" + integrity sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg== + dependencies: + node-addon-api "^3.2.1" + node-gyp-build "^4.3.0" + +"@sinclair/typebox@^0.23.3": + version "0.23.5" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.23.5.tgz#93f7b9f4e3285a7a9ade7557d9a8d36809cbc47d" + integrity sha512-AFBVi/iT4g20DHoujvMH1aEDn8fGJh4xsRGCP6d8RpLPMqsNPvW01Jcn0QysXTsg++/xj25NmJsGyH9xug/wKg== + +"@sinonjs/commons@^1.7.0": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^9.1.1": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" + integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + +"@ts-morph/common@~0.12.3": + version "0.12.3" + resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.12.3.tgz#a96e250217cd30e480ab22ec6a0ebbe65fd784ff" + integrity sha512-4tUmeLyXJnJWvTFOKtcNJ1yh0a3SsTLi2MUoyj8iUNznFRN1ZquaNe7Oukqrnki2FzZkm0J9adCNLDZxUzvj+w== + dependencies: + fast-glob "^3.2.7" + minimatch "^3.0.4" + mkdirp "^1.0.4" + path-browserify "^1.0.1" + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + +"@types/babel__core@^7.1.14": + version "7.1.19" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" + integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.17.1" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" + integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== + dependencies: + "@babel/types" "^7.3.0" + +"@types/bcrypt@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/bcrypt/-/bcrypt-5.0.0.tgz#a835afa2882d165aff5690893db314eaa98b9f20" + integrity sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw== + dependencies: + "@types/node" "*" + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bytes@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/bytes/-/bytes-3.1.1.tgz#67a876422e660dc4c10a27f3e5bcfbd5455f01d0" + integrity sha512-lOGyCnw+2JVPKU3wIV0srU0NyALwTBJlVSx5DfMQOFuuohA8y9S8orImpuIQikZ0uIQ8gehrRjxgQC1rLRi11w== + +"@types/cache-manager@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/cache-manager/-/cache-manager-4.0.0.tgz#6522a64432bb900d7e6654270fec21bc3c3de139" + integrity sha512-uGnPOCM3PtlqZagds3i8mNyEwKLgZpKgswqmlF2ahmh4D1TN1aLYxYez2PDFDy42IGwLTbuHWSiF62I2jouM7g== + +"@types/compression@^1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@types/compression/-/compression-1.7.2.tgz#7cc1cdb01b4730eea284615a68fc70a2cdfd5e71" + integrity sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg== + dependencies: + "@types/express" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/cookiejar@*": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8" + integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog== + +"@types/cors@^2.8.12": + version "2.8.12" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" + integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== + +"@types/cron@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/cron/-/cron-2.0.0.tgz#4fe75f2720a3b69a1f7b80e656749f4c2c96d727" + integrity sha512-xZM08fqvwIXgghtPVkSPKNgC+JoMQ2OHazEvyTKnNf7aWu1aB6/4lBbQFrb03Td2cUGG7ITzMv3mFYnMu6xRaQ== + dependencies: + "@types/luxon" "*" + "@types/node" "*" + +"@types/crypto-js@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.1.tgz#602859584cecc91894eb23a4892f38cfa927890d" + integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA== + +"@types/eslint-scope@^3.7.3": + version "3.7.3" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" + integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "8.4.3" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.3.tgz#5c92815a3838b1985c90034cd85f26f59d9d0ece" + integrity sha512-YP1S7YJRMPs+7KZKDb9G63n8YejIwW9BALq7a5j2+H4yl6iOv9CB29edho+cuFRrvmJbbaH2yiVChKLJVysDGw== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + +"@types/express-serve-static-core@^4.17.18": + version "4.17.29" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz#2a1795ea8e9e9c91b4a4bbe475034b20c1ec711c" + integrity sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/graceful-fs@^4.1.3": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^28.1.1", "@types/jest@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-28.1.3.tgz#52f3f3e50ce59191ff5fbb1084896cc0cf30c9ce" + integrity sha512-Tsbjk8Y2hkBaY/gJsataeb4q9Mubw9EOz7+4RjPkzD5KjTvHHs7cpws22InaoXxAVAhF5HfFbzJjo6oKWqSZLw== + dependencies: + jest-matcher-utils "^28.0.0" + pretty-format "^28.0.0" + +"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/jsonwebtoken@*", "@types/jsonwebtoken@8.5.8": + version "8.5.8" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz#01b39711eb844777b7af1d1f2b4cf22fda1c0c44" + integrity sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A== + dependencies: + "@types/node" "*" + +"@types/lodash@^4.14.182": + version "4.14.182" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" + integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== + +"@types/luxon@*": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-2.3.2.tgz#8a3f2cdd4858ce698b56cd8597d9243b8e9d3c65" + integrity sha512-WOehptuhKIXukSUUkRgGbj2c997Uv/iUgYgII8U7XLJqq9W2oF0kQ6frEznRQbdurioz+L/cdaIm4GutTQfgmA== + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + +"@types/minimatch@^3.0.3": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + +"@types/minimist@^1.2.0": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" + integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== + +"@types/morgan@^1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@types/morgan/-/morgan-1.9.3.tgz#ae04180dff02c437312bc0cfb1e2960086b2f540" + integrity sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q== + dependencies: + "@types/node" "*" + +"@types/ms@^0.7.31": + version "0.7.31" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" + integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== + +"@types/multer@^1.4.7": + version "1.4.7" + resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.7.tgz#89cf03547c28c7bbcc726f029e2a76a7232cc79e" + integrity sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA== + dependencies: + "@types/express" "*" + +"@types/node@*", "@types/node@>=12", "@types/node@^18.0.0": + version "18.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a" + integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA== + +"@types/node@^14.0.1": + version "14.18.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.21.tgz#0155ee46f6be28b2ff0342ca1a9b9fd4468bef41" + integrity sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q== + +"@types/node@^17.0.40": + version "17.0.45" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" + integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== + +"@types/normalize-package-data@^2.4.0": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" + integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/passport-jwt@^3.0.6": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/passport-jwt/-/passport-jwt-3.0.6.tgz#41cc8b5803d5f5f06eb33e19c453b42716def4f1" + integrity sha512-cmAAMIRTaEwpqxlrZyiEY9kdibk94gP5KTF8AT1Ra4rWNZYHNMreqhKUEeC5WJtuN5SJZjPQmV+XO2P5PlnvNQ== + dependencies: + "@types/express" "*" + "@types/jsonwebtoken" "*" + "@types/passport-strategy" "*" + +"@types/passport-strategy@*": + version "0.2.35" + resolved "https://registry.yarnpkg.com/@types/passport-strategy/-/passport-strategy-0.2.35.tgz#e52f5212279ea73f02d9b06af67efe9cefce2d0c" + integrity sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g== + dependencies: + "@types/express" "*" + "@types/passport" "*" + +"@types/passport@*": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.9.tgz#b32fa8f7485dace77a9b58e82d0c92908f6e8387" + integrity sha512-9+ilzUhmZQR4JP49GdC2O4UdDE3POPLwpmaTC/iLkW7l0TZCXOo1zsTnnlXPq6rP1UsUZPfbAV4IUdiwiXyC7g== + dependencies: + "@types/express" "*" + +"@types/prettier@^2.1.5": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.3.tgz#68ada76827b0010d0db071f739314fa429943d0a" + integrity sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg== + +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/response-time@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@types/response-time/-/response-time-2.3.5.tgz#e85ff348caefd0f8d3e8902424c681a59aafc31e" + integrity sha512-4ANzp+I3K7sztFFAGPALWBvSl4ayaDSKzI2Bok+WNz+en2eB2Pvk6VCjR47PBXBWOkEg2r4uWpZOlXA5DNINOQ== + dependencies: + "@types/express" "*" + "@types/node" "*" + +"@types/serve-static@*": + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/superagent@*": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-4.1.15.tgz#63297de457eba5e2bc502a7609426c4cceab434a" + integrity sha512-mu/N4uvfDN2zVQQ5AYJI/g4qxn2bHB6521t1UuH09ShNWjebTqN0ZFuYK9uYjcgmI0dTQEs+Owi1EO6U0OkOZQ== + dependencies: + "@types/cookiejar" "*" + "@types/node" "*" + +"@types/supertest@^2.0.12": + version "2.0.12" + resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.12.tgz#ddb4a0568597c9aadff8dbec5b2e8fddbe8692fc" + integrity sha512-X3HPWTwXRerBZS7Mo1k6vMVR1Z6zmJcDVn5O/31whe0tnjE4te6ZJSJGq1RiqHPjzPdMTfjCFogDJmwng9xHaQ== + dependencies: + "@types/superagent" "*" + +"@types/ua-parser-js@^0.7.36": + version "0.7.36" + resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz#9bd0b47f26b5a3151be21ba4ce9f5fa457c5f190" + integrity sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ== + +"@types/uuid@^8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + +"@types/webidl-conversions@*": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz#e33bc8ea812a01f63f90481c666334844b12a09e" + integrity sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q== + +"@types/whatwg-url@^8.2.1": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.2.tgz#749d5b3873e845897ada99be4448041d4cc39e63" + integrity sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA== + dependencies: + "@types/node" "*" + "@types/webidl-conversions" "*" + +"@types/ws@^8.5.3": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^17.0.8": + version "17.0.10" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.10.tgz#591522fce85d8739bca7b8bb90d048e4478d186a" + integrity sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^5.27.0", "@typescript-eslint/eslint-plugin@^5.29.0": + version "5.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz#c67794d2b0fd0b4a47f50266088acdc52a08aab6" + integrity sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w== + dependencies: + "@typescript-eslint/scope-manager" "5.29.0" + "@typescript-eslint/type-utils" "5.29.0" + "@typescript-eslint/utils" "5.29.0" + debug "^4.3.4" + functional-red-black-tree "^1.0.1" + ignore "^5.2.0" + regexpp "^3.2.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.27.0", "@typescript-eslint/parser@^5.29.0": + version "5.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.29.0.tgz#41314b195b34d44ff38220caa55f3f93cfca43cf" + integrity sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw== + dependencies: + "@typescript-eslint/scope-manager" "5.29.0" + "@typescript-eslint/types" "5.29.0" + "@typescript-eslint/typescript-estree" "5.29.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.29.0": + version "5.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.29.0.tgz#2a6a32e3416cb133e9af8dcf54bf077a916aeed3" + integrity sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA== + dependencies: + "@typescript-eslint/types" "5.29.0" + "@typescript-eslint/visitor-keys" "5.29.0" + +"@typescript-eslint/type-utils@5.29.0": + version "5.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.29.0.tgz#241918001d164044020b37d26d5b9f4e37cc3d5d" + integrity sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg== + dependencies: + "@typescript-eslint/utils" "5.29.0" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.29.0": + version "5.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.29.0.tgz#7861d3d288c031703b2d97bc113696b4d8c19aab" + integrity sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg== + +"@typescript-eslint/typescript-estree@5.29.0": + version "5.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.29.0.tgz#e83d19aa7fd2e74616aab2f25dfbe4de4f0b5577" + integrity sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ== + dependencies: + "@typescript-eslint/types" "5.29.0" + "@typescript-eslint/visitor-keys" "5.29.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.29.0": + version "5.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.29.0.tgz#775046effd5019667bd086bcf326acbe32cd0082" + integrity sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.29.0" + "@typescript-eslint/types" "5.29.0" + "@typescript-eslint/typescript-estree" "5.29.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/visitor-keys@5.29.0": + version "5.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.29.0.tgz#7a4749fa7ef5160c44a451bf060ac1dc6dfb77ee" + integrity sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ== + dependencies: + "@typescript-eslint/types" "5.29.0" + eslint-visitor-keys "^3.3.0" + +"@webassemblyjs/ast@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" + integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + +"@webassemblyjs/floating-point-hex-parser@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" + integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== + +"@webassemblyjs/helper-api-error@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" + integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== + +"@webassemblyjs/helper-buffer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" + integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== + +"@webassemblyjs/helper-numbers@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" + integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" + integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== + +"@webassemblyjs/helper-wasm-section@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" + integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + +"@webassemblyjs/ieee754@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" + integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" + integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" + integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== + +"@webassemblyjs/wasm-edit@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" + integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-wasm-section" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-opt" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + "@webassemblyjs/wast-printer" "1.11.1" + +"@webassemblyjs/wasm-gen@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" + integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wasm-opt@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" + integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + +"@webassemblyjs/wasm-parser@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" + integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wast-printer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" + integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +JSONStream@^1.0.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +accept-language-parser@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/accept-language-parser/-/accept-language-parser-1.5.0.tgz#8877c54040a8dcb59e0a07d9c1fde42298334791" + integrity sha512-QhyTbMLYo0BBGg1aWbeMG4ekWtds/31BrEU+DONOg/7ax23vxpL03Pb7/zBmha2v7vdD3AyzZVWBVGEZxKOXWw== + +accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-import-assertions@^1.7.6: + version "1.8.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" + integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + +add-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" + integrity sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ== + +agent-base@6, agent-base@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +agentkeepalive@^4.1.3, agentkeepalive@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" + integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== + dependencies: + debug "^4.1.0" + depd "^1.1.2" + humanize-ms "^1.2.1" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv-formats@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@8.9.0: + version "8.9.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.9.0.tgz#738019146638824dea25edcf299dcba1b0e7eb18" + integrity sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.12.6: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" + integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" + integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== + +ansi-regex@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +append-field@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" + integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== + +"aproba@^1.0.3 || ^2.0.0", aproba@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +archiver-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" + integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== + dependencies: + glob "^7.1.4" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^2.0.0" + +archiver@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.1.tgz#21e92811d6f09ecfce649fbefefe8c79e57cbbb6" + integrity sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w== + dependencies: + archiver-utils "^2.1.0" + async "^3.2.3" + buffer-crc32 "^0.2.1" + readable-stream "^3.6.0" + readdir-glob "^1.0.0" + tar-stream "^2.2.0" + zip-stream "^4.1.0" + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +are-we-there-yet@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz#ba20bd6b553e31d62fc8c31bd23d22b95734390d" + integrity sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-differ@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" + integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +array-ify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" + integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== + +array-includes@^3.1.4: + version "3.1.5" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" + integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + get-intrinsic "^1.1.1" + is-string "^1.0.7" + +array-timsort@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-timsort/-/array-timsort-1.0.3.tgz#3c9e4199e54fb2b9c3fe5976396a21614ef0d926" + integrity sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.flat@^1.2.5: + version "1.3.0" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b" + integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.2" + es-shim-unscopables "^1.0.0" + +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== + +arrify@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== + +asap@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +async@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" + integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== + +async@^3.2.3: + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +axios@0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + +babel-jest@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.1.tgz#2a3a4ae50964695b2d694ccffe4bec537c5a3586" + integrity sha512-MEt0263viUdAkTq5D7upHPNxvt4n9uLUGa6pPz3WviNBMtOmStb1lIXS3QobnoqM+qnH+vr4EKlvhe8QcmxIYw== + dependencies: + "@jest/transform" "^28.1.1" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^28.1.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.1.tgz#5e055cdcc47894f28341f87f5e35aad2df680b11" + integrity sha512-NovGCy5Hn25uMJSAU8FaHqzs13cFoOI4lhIujiepssjCKRsAo3TA734RDWSGxuFTsUJXerYOqQQodlxgmtqbzw== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-28.1.1.tgz#5b6e5e69f963eb2d70f739c607b8f723c0ee75e4" + integrity sha512-FCq9Oud0ReTeWtcneYf/48981aTfXYuB9gbU4rBNNJVBSQ6ssv7E6v/qvbBxtOWwZFXjLZwpg+W3q7J6vhH25g== + dependencies: + babel-plugin-jest-hoist "^28.1.1" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +basic-auth@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" + integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== + dependencies: + safe-buffer "5.1.2" + +bcrypt@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.1.tgz#f1a2c20f208e2ccdceea4433df0c8b2c54ecdf71" + integrity sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.0" + node-addon-api "^3.1.0" + +before-after-hook@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" + integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== + +big-integer@^1.6.17: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + +bin-links@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-3.0.1.tgz#cc70ffb481988b22c527d3e6e454787876987a49" + integrity sha512-9vx+ypzVhASvHTS6K+YSGf7nwQdANoz7v6MTC0aCtYnOEZ87YvMf81aY737EZnGZdpbRM3sfWjO9oWkKmuIvyQ== + dependencies: + cmd-shim "^5.0.0" + mkdirp-infer-owner "^2.0.0" + npm-normalize-package-bin "^1.0.0" + read-cmd-shim "^3.0.0" + rimraf "^3.0.0" + write-file-atomic "^4.0.0" + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg== + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + +bl@^4.0.3, bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA== + +body-parser@1.20.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" + integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.10.3" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + +bowser@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" + integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.14.5, browserslist@^4.20.2: + version "4.21.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.0.tgz#7ab19572361a140ecd1e023e2c1ed95edda0cefe" + integrity sha512-UQxE0DIhRB5z/zDz9iA03BOfxaN2+GQdBYH/2WrSIWEUrnpzTPJbhqt+umq6r3acaPRTW1FNTkrcp0PXgtFkvA== + dependencies: + caniuse-lite "^1.0.30001358" + electron-to-chromium "^1.4.164" + node-releases "^2.0.5" + update-browserslist-db "^1.0.0" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +bson@^4.6.2, bson@^4.6.3: + version "4.6.4" + resolved "https://registry.yarnpkg.com/bson/-/bson-4.6.4.tgz#e66d4a334f1ab230dfcfb9ec4ea9091476dd372e" + integrity sha512-TdQ3FzguAu5HKPPlr0kYQCyrYUYh8tFM+CMTpxjNzVzxeiJY00Rtuj3LXLHSgiGvmaWlZ8PE+4KyM2thqE38pQ== + dependencies: + buffer "^5.6.0" + +buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer-indexof-polyfill@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== + +buffer@^5.5.0, buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ== + +builtins@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" + integrity sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ== + +builtins@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" + integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ== + dependencies: + semver "^7.0.0" + +busboy@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + +byte-size@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-7.0.1.tgz#b1daf3386de7ab9d706b941a748dbfc71130dee3" + integrity sha512-crQdqyCwhokxwV1UyDzLZanhkugAgft7vt0qbbdt60C6Zf3CAiGmtUCylbtYwrU6loOUw3euGrNtW1J651ot1A== + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +cacache@^15.0.5, cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + +cacache@^16.0.0, cacache@^16.0.6, cacache@^16.1.0: + version "16.1.1" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.1.tgz#4e79fb91d3efffe0630d5ad32db55cc1b870669c" + integrity sha512-VDKN+LHyCQXaaYZ7rA/qtkURU+/yYhviUdvqEv2LT6QPZU8jpyzEkEVAcKlKLt5dJ5BRp11ym8lo3NKLluEPLg== + dependencies: + "@npmcli/fs" "^2.1.0" + "@npmcli/move-file" "^2.0.0" + chownr "^2.0.0" + fs-minipass "^2.1.0" + glob "^8.0.1" + infer-owner "^1.0.4" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^9.0.0" + tar "^6.1.11" + unique-filename "^1.1.1" + +cache-manager@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cache-manager/-/cache-manager-4.0.1.tgz#185b1d1aa1385fbb4fb0ec88fda7676f566a15b8" + integrity sha512-JWdtjdX8e0e6eMehAZsdJvBMvHn/pVQGYUjgzc1ILFH0vtcffb9R7XIEAqfYgEeaVJVCOSP4+dxCius+ciW0RA== + dependencies: + async "3.2.3" + lodash.clonedeep "^4.5.0" + lru-cache "^7.10.1" + +cachedir@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.2.0.tgz#19afa4305e05d79e417566882e0c8f960f62ff0e" + integrity sha512-VvxA0xhNqIIfg0V9AmJkDg91DaJwryutH5rVEZAhcNi4iJFj9f+QxmAjgK1LT9I8OgToX27fypX6/MeCXVbBjQ== + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0, callsites@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase-keys@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" + integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== + dependencies: + camelcase "^5.3.1" + map-obj "^4.0.0" + quick-lru "^4.0.1" + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001358: + version "1.0.30001358" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001358.tgz#473d35dabf5e448b463095cab7924e96ccfb8c00" + integrity sha512-hvp8PSRymk85R20bsDra7ZTCpSVGN/PAz9pSAjPSjKC+rNmnUk5vCRgJwiTT/O4feQ/yu/drvZYpKxxhbFuChw== + +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ== + dependencies: + traverse ">=0.3.0 <0.4" + +chalk@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +check-disk-space@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/check-disk-space/-/check-disk-space-3.3.0.tgz#2a8f4c9542ed71a70878fc28fda7e265d40942ec" + integrity sha512-Hvr+Nr01xSSvuCpXvJ8oZ2iXjIu4XT3uHbw3g7F/Uiw6O5xk8c/Ot7ZGFDaTRDf2Bz8AdWA4DvpAgCJVKt8arw== + +chokidar@3.5.3, chokidar@^3.5.1, chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +ci-info@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.2.tgz#6d2967ffa407466481c6c90b6e16b3098f080128" + integrity sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg== + +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + +class-transformer@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" + integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== + +class-validator@^0.13.2: + version "0.13.2" + resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.13.2.tgz#64b031e9f3f81a1e1dcd04a5d604734608b24143" + integrity sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw== + dependencies: + libphonenumber-js "^1.9.43" + validator "^13.7.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +clear-module@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/clear-module/-/clear-module-4.1.2.tgz#5a58a5c9f8dccf363545ad7284cad3c887352a80" + integrity sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw== + dependencies: + parent-module "^2.0.0" + resolve-from "^5.0.0" + +cli-color@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.2.tgz#e295addbae470800def0254183c648531cdf4e3f" + integrity sha512-g4JYjrTW9MGtCziFNjkqp3IMpGhnJyeB0lOtRPjQkYhXzKYr6tYnXKyEVnMzITxhpbahsEW9KsxOYIDKwcsIBw== + dependencies: + d "^1.0.1" + es5-ext "^0.10.59" + es6-iterator "^2.0.3" + memoizee "^0.4.15" + timers-ext "^0.1.7" + +cli-cursor@3.1.0, cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== + dependencies: + restore-cursor "^2.0.0" + +cli-spinners@2.6.1, cli-spinners@^2.5.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" + integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== + +cli-table3@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.2.tgz#aaf5df9d8b5bf12634dc8b3040806a0c07120d2a" + integrity sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +cli-width@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" + integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + +cmd-shim@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-4.1.0.tgz#b3a904a6743e9fede4148c6f3800bf2a08135bdd" + integrity sha512-lb9L7EM4I/ZRVuljLPEtUJOP+xiQVknZ4ZMpMgEp4JzNldPb27HU03hi6K1/6CoIuit/Zm/LQXySErFeXxDprw== + dependencies: + mkdirp-infer-owner "^2.0.0" + +cmd-shim@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-5.0.0.tgz#8d0aaa1a6b0708630694c4dbde070ed94c707724" + integrity sha512-qkCtZ59BidfEwHltnJwkyVZn+XQojdAySM1D1gSeh11Z4pW1Kpolkyo53L5noc0nrxmIvyFwTmJRo4xs7FFLPw== + dependencies: + mkdirp-infer-owner "^2.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +code-block-writer@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-11.0.0.tgz#5956fb186617f6740e2c3257757fea79315dd7d4" + integrity sha512-GEqWvEWWsOvER+g9keO4ohFoD3ymwyCnqY3hoTr7GZipYFwEhMHJw+TtV0rfgRhNImM6QWZGO2XYjlJVyYT62w== + dependencies: + tslib "2.3.1" + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +color-convert@^1.9.0, color-convert@^1.9.3: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.6.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color-support@^1.1.2, color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +color@^3.1.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== + dependencies: + color-convert "^1.9.3" + color-string "^1.6.0" + +colorspace@1.1.x: + version "1.1.4" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" + integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== + dependencies: + color "^3.1.3" + text-hex "1.0.x" + +columnify@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3" + integrity sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q== + dependencies: + strip-ansi "^6.0.1" + wcwidth "^1.0.0" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +commander@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.3.0.tgz#f619114a5a2d2054e0d9ff1b31d5ccf89255e26b" + integrity sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw== + +comment-json@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-4.2.2.tgz#5fae70a94e0c8f84a077bd31df5aa5269252f293" + integrity sha512-H8T+kl3nZesZu41zO2oNXIJWojNeK3mHxCLrsBNu6feksBXsgb+PtYz5daP5P86A0F3sz3840KVYehr04enISQ== + dependencies: + array-timsort "^1.0.3" + core-util-is "^1.0.3" + esprima "^4.0.1" + has-own-prop "^2.0.0" + repeat-string "^1.6.1" + +commitizen@^4.0.3: + version "4.2.4" + resolved "https://registry.yarnpkg.com/commitizen/-/commitizen-4.2.4.tgz#a3e5b36bd7575f6bf6e7aa19dbbf06b0d8f37165" + integrity sha512-LlZChbDzg3Ir3O2S7jSo/cgWp5/QwylQVr59K4xayVq8S4/RdKzSyJkghAiZZHfhh5t4pxunUoyeg0ml1q/7aw== + dependencies: + cachedir "2.2.0" + cz-conventional-changelog "3.2.0" + dedent "0.7.0" + detect-indent "6.0.0" + find-node-modules "^2.1.2" + find-root "1.1.0" + fs-extra "8.1.0" + glob "7.1.4" + inquirer "6.5.2" + is-utf8 "^0.2.1" + lodash "^4.17.20" + minimist "1.2.5" + strip-bom "4.0.0" + strip-json-comments "3.0.1" + +common-ancestor-path@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" + integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== + +compare-func@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" + integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== + dependencies: + array-ify "^1.0.0" + dot-prop "^5.1.0" + +component-emitter@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +compress-commons@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" + integrity sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ== + dependencies: + buffer-crc32 "^0.2.13" + crc32-stream "^4.0.2" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +concat-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +concat-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" + integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.0.2" + typedarray "^0.0.6" + +config-chain@^1.1.12: + version "1.1.13" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + +consola@^2.15.0: + version "2.15.3" + resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" + integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== + +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +conventional-changelog-angular@^5.0.12: + version "5.0.13" + resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" + integrity sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA== + dependencies: + compare-func "^2.0.0" + q "^1.5.1" + +conventional-changelog-core@^4.2.2: + version "4.2.4" + resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz#e50d047e8ebacf63fac3dc67bf918177001e1e9f" + integrity sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg== + dependencies: + add-stream "^1.0.0" + conventional-changelog-writer "^5.0.0" + conventional-commits-parser "^3.2.0" + dateformat "^3.0.0" + get-pkg-repo "^4.0.0" + git-raw-commits "^2.0.8" + git-remote-origin-url "^2.0.0" + git-semver-tags "^4.1.1" + lodash "^4.17.15" + normalize-package-data "^3.0.0" + q "^1.5.1" + read-pkg "^3.0.0" + read-pkg-up "^3.0.0" + through2 "^4.0.0" + +conventional-changelog-preset-loader@^2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz#14a855abbffd59027fd602581f1f34d9862ea44c" + integrity sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g== + +conventional-changelog-writer@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz#e0757072f045fe03d91da6343c843029e702f359" + integrity sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ== + dependencies: + conventional-commits-filter "^2.0.7" + dateformat "^3.0.0" + handlebars "^4.7.7" + json-stringify-safe "^5.0.1" + lodash "^4.17.15" + meow "^8.0.0" + semver "^6.0.0" + split "^1.0.0" + through2 "^4.0.0" + +conventional-commit-types@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/conventional-commit-types/-/conventional-commit-types-3.0.0.tgz#7c9214e58eae93e85dd66dbfbafe7e4fffa2365b" + integrity sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg== + +conventional-commits-filter@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz#f8d9b4f182fce00c9af7139da49365b136c8a0b3" + integrity sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA== + dependencies: + lodash.ismatch "^4.4.0" + modify-values "^1.0.0" + +conventional-commits-parser@^3.2.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz#a7d3b77758a202a9b2293d2112a8d8052c740972" + integrity sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q== + dependencies: + JSONStream "^1.0.4" + is-text-path "^1.0.1" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" + +conventional-recommended-bump@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz#cfa623285d1de554012f2ffde70d9c8a22231f55" + integrity sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw== + dependencies: + concat-stream "^2.0.0" + conventional-changelog-preset-loader "^2.3.4" + conventional-commits-filter "^2.0.7" + conventional-commits-parser "^3.2.0" + git-raw-commits "^2.0.8" + git-semver-tags "^4.1.1" + meow "^8.0.0" + q "^1.5.1" + +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +cookie@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + +cookiejar@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" + integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== + +core-util-is@^1.0.3, core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cors@2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +cosmiconfig-typescript-loader@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-2.0.1.tgz#5622bb1eb87d293570bcc3a57f406940e0960113" + integrity sha512-B9s6sX/omXq7I6gC6+YgLmrBFMJhPWew7ty/X5Tuwtd2zOSgWaUdXjkuVwbe3qqcdETo60+1nSVMekq//LIXVA== + dependencies: + cosmiconfig "^7" + ts-node "^10.8.0" + +cosmiconfig@^7, cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +crc-32@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + +crc32-stream@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007" + integrity sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w== + dependencies: + crc-32 "^1.2.0" + readable-stream "^3.4.0" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cron@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cron/-/cron-2.0.0.tgz#15c6bf37c1cebf6da1d7a688b9ba1c68338bfe6b" + integrity sha512-RPeRunBCFr/WEo7WLp8Jnm45F/ziGJiHVvVQEBSDTSGu6uHW49b2FOP2O14DcXlGJRLhwE7TIoDzHHK4KmlL6g== + dependencies: + luxon "^1.23.x" + +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-js@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" + integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== + +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + +cspell-gitignore@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cspell-gitignore/-/cspell-gitignore-6.1.2.tgz#9ce6f6465c1e800d7d0bd9eec432e59ebed0c9c5" + integrity sha512-9P4ltD5DF/Dogz/+IgW8BZjqvgbOghg2OKk165+ilKgoQc73zHQy8bZRdJsDo2NBevmT8Z9RswEN37pQvzmMqQ== + dependencies: + cspell-glob "^6.1.2" + find-up "^5.0.0" + +cspell-glob@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cspell-glob/-/cspell-glob-6.1.2.tgz#1081bce9a480f6de61dec7058a6faa31dbff248e" + integrity sha512-+EN4DEyK8ohwZLShPw9vhU6114fnGYi8q4IQAGc5qCCcnMTgb1H3N840YyG+EuP0Q1o3TwMYMA+B+tw4w+yQSg== + dependencies: + micromatch "^4.0.5" + +cspell-io@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cspell-io/-/cspell-io-6.1.2.tgz#64fa2b8728532f5e04dc463bfc161a2df93b90bc" + integrity sha512-WVRKjOzB3BgwJPk4hWH19jiqXzbtWGzJ1yNLaB2r3KYCDh+FYT4jVaHb5DWERASahvukb05go7G623FuYeoY3Q== + +cspell-lib@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cspell-lib/-/cspell-lib-6.1.2.tgz#9c61c4501835452ce632c005ece7d07b6a8ee6bc" + integrity sha512-oJVAvxnP6jsiglLIdFy9agO5vfArTQdLweRjkR8SbVPoYQ8vyDfrbQT9J+K+ekKpCvpzKKzjleOJHHwJFeltGQ== + dependencies: + "@cspell/cspell-bundled-dicts" "^6.1.2" + "@cspell/cspell-pipe" "^6.1.2" + "@cspell/cspell-types" "^6.1.2" + clear-module "^4.1.2" + comment-json "^4.2.2" + configstore "^5.0.1" + cosmiconfig "^7.0.1" + cspell-glob "^6.1.2" + cspell-io "^6.1.2" + cspell-trie-lib "^6.1.2" + fast-equals "^4.0.1" + find-up "^5.0.0" + fs-extra "^10.1.0" + gensequence "^3.1.1" + import-fresh "^3.3.0" + resolve-from "^5.0.0" + resolve-global "^1.0.0" + vscode-languageserver-textdocument "^1.0.5" + vscode-uri "^3.0.3" + +cspell-trie-lib@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cspell-trie-lib/-/cspell-trie-lib-6.1.2.tgz#aaa933b7c7af2b58eff920179cfdaf4e435357ec" + integrity sha512-VQasYB6WYQZz71Uo0Z4K/i6qOZpBqOh0SbKQr6n0lkejnmoAv324SKJqKyXyizWCQQvWDq+ax18bW+KBFNBzxQ== + dependencies: + "@cspell/cspell-pipe" "^6.1.2" + fs-extra "^10.1.0" + gensequence "^3.1.1" + +cspell@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cspell/-/cspell-6.1.2.tgz#b17be28dda221867627973840dc83447c84ff537" + integrity sha512-w4lGfzNl3m1dfagyZvr28V1nK/E+y8zoKVlE158JI/iVNGO/R2okrcNB1s+9xXSmYjJ8Xx6dhupO0XxKuagDSQ== + dependencies: + "@cspell/cspell-pipe" "^6.1.2" + chalk "^4.1.2" + commander "^9.3.0" + cspell-gitignore "^6.1.2" + cspell-glob "^6.1.2" + cspell-lib "^6.1.2" + fast-json-stable-stringify "^2.1.0" + file-entry-cache "^6.0.1" + fs-extra "^10.1.0" + get-stdin "^8.0.0" + glob "^8.0.3" + imurmurhash "^0.1.4" + semver "^7.3.7" + strip-ansi "^6.0.1" + vscode-uri "^3.0.3" + +cz-conventional-changelog@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cz-conventional-changelog/-/cz-conventional-changelog-3.2.0.tgz#6aef1f892d64113343d7e455529089ac9f20e477" + integrity sha512-yAYxeGpVi27hqIilG1nh4A9Bnx4J3Ov+eXy4koL3drrR+IO9GaWPsKjik20ht608Asqi8TQPf0mczhEeyAtMzg== + dependencies: + chalk "^2.4.1" + commitizen "^4.0.3" + conventional-commit-types "^3.0.0" + lodash.map "^4.5.1" + longest "^2.0.1" + word-wrap "^1.0.3" + optionalDependencies: + "@commitlint/load" ">6.1.1" + +cz-conventional-changelog@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/cz-conventional-changelog/-/cz-conventional-changelog-3.3.0.tgz#9246947c90404149b3fe2cf7ee91acad3b7d22d2" + integrity sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw== + dependencies: + chalk "^2.4.1" + commitizen "^4.0.3" + conventional-commit-types "^3.0.0" + lodash.map "^4.5.1" + longest "^2.0.1" + word-wrap "^1.0.3" + optionalDependencies: + "@commitlint/load" ">6.1.1" + +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + +dargs@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" + integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== + +dateformat@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" + integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== + +dayjs@^1.8.34: + version "1.11.3" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.3.tgz#4754eb694a624057b9ad2224b67b15d552589258" + integrity sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A== + +debug@2.6.9, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4, debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debuglog@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw== + +decamelize-keys@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" + integrity sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg== + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== + +dedent@0.7.0, dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA== + dependencies: + clone "^1.0.2" + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +define-properties@^1.1.3, define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +denque@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.0.1.tgz#bcef4c1b80dc32efe97515744f21a4229ab8934a" + integrity sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ== + +depd@2.0.0, depd@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +depd@^1.1.2, depd@~1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +deprecation@^2.0.0, deprecation@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-file@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" + integrity sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q== + +detect-indent@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" + integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA== + +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + integrity sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g== + +detect-indent@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" + integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== + +detect-libc@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +dezalgo@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" + integrity sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ== + dependencies: + asap "^2.0.0" + wrappy "1" + +dezalgo@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" + integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== + dependencies: + asap "^2.0.0" + wrappy "1" + +diff-sequences@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6" + integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dot-prop@^5.1.0, dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +dot-prop@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" + integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== + dependencies: + is-obj "^2.0.0" + +dotenv-expand@8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-8.0.3.tgz#29016757455bcc748469c83a19b36aaf2b83dd6e" + integrity sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg== + +dotenv@16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" + integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== + +dotenv@~10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA== + dependencies: + readable-stream "^2.0.2" + +duplexer@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.4.164: + version "1.4.165" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.165.tgz#a1ae079a4412b0c2d3bf6908e8db54511fb0bbac" + integrity sha512-DKQW1lqUSAYQvn9dnpK7mWaDpWbNOXQLXhfCi7Iwx0BKxdZOxkKcCyKw1l3ihWWW5iWSxKKbhEUoNRoHvl/hbA== + +emittery@^0.10.2: + version "0.10.2" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" + integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +encoding@^0.1.12, encoding@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enhanced-resolve@^5.0.0, enhanced-resolve@^5.7.0, enhanced-resolve@^5.9.3: + version "5.9.3" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88" + integrity sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +enquirer@~2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +entities@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +envinfo@^7.7.4: + version "7.8.1" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" + integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5: + version "1.20.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814" + integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + function.prototype.name "^1.1.5" + get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-property-descriptors "^1.0.0" + has-symbols "^1.0.3" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-weakref "^1.0.2" + object-inspect "^1.12.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + regexp.prototype.flags "^1.4.3" + string.prototype.trimend "^1.0.5" + string.prototype.trimstart "^1.0.5" + unbox-primitive "^1.0.2" + +es-module-lexer@^0.9.0: + version "0.9.3" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" + integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== + +es-shim-unscopables@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" + integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + dependencies: + has "^1.0.3" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.59, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: + version "0.10.61" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.61.tgz#311de37949ef86b6b0dcea894d1ffedb909d3269" + integrity sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +es6-weak-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + dependencies: + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + es6-symbol "^3.1.1" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" + integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== + +eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== + dependencies: + debug "^3.2.7" + resolve "^1.20.0" + +eslint-module-utils@^2.7.3: + version "2.7.3" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" + integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ== + dependencies: + debug "^3.2.7" + find-up "^2.1.0" + +eslint-plugin-import@^2.26.0: + version "2.26.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" + integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== + dependencies: + array-includes "^3.1.4" + array.prototype.flat "^1.2.5" + debug "^2.6.9" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.7.3" + has "^1.0.3" + is-core-module "^2.8.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.values "^1.1.5" + resolve "^1.22.0" + tsconfig-paths "^3.14.1" + +eslint-scope@5.1.1, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint@^8.17.0, eslint@^8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.18.0.tgz#78d565d16c993d0b73968c523c0446b13da784fd" + integrity sha512-As1EfFMVk7Xc6/CvhssHUjsAQSkpfXvUGMFC3ce8JDe6WvqCgRrLOBQbVpsBFr1X1V+RACOadnzVvcUS5ni2bA== + dependencies: + "@eslint/eslintrc" "^1.3.0" + "@humanwhocodes/config-array" "^0.9.2" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.1.1" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.3.0" + espree "^9.3.2" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^6.0.1" + globals "^13.15.0" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^9.3.2: + version "9.3.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596" + integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA== + dependencies: + acorn "^8.7.1" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.3.0" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== + dependencies: + d "1" + es5-ext "~0.10.14" + +eventemitter3@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +exceljs@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/exceljs/-/exceljs-4.3.0.tgz#939bc0d4c59c200acadb7051be34d25c109853c4" + integrity sha512-hTAeo5b5TPvf8Z02I2sKIT4kSfCnOO2bCxYX8ABqODCdAjppI3gI9VYiGCQQYVcBaBSKlFDMKlAQRqC+kV9O8w== + dependencies: + archiver "^5.0.0" + dayjs "^1.8.34" + fast-csv "^4.3.1" + jszip "^3.5.0" + readable-stream "^3.6.0" + saxes "^5.0.1" + tmp "^0.2.0" + unzipper "^0.10.11" + uuid "^8.3.0" + +execa@^4.0.2: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expand-tilde@^2.0.0, expand-tilde@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + integrity sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw== + dependencies: + homedir-polyfill "^1.0.1" + +expect@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-28.1.1.tgz#ca6fff65f6517cf7220c2e805a49c19aea30b420" + integrity sha512-/AANEwGL0tWBwzLNOvO0yUdy2D52jVdNXppOqswC49sxMN2cPWsGCQdzuIf9tj6hHoBQzNvx75JUYuQAckPo3w== + dependencies: + "@jest/expect-utils" "^28.1.1" + jest-get-type "^28.0.2" + jest-matcher-utils "^28.1.1" + jest-message-util "^28.1.1" + jest-util "^28.1.1" + +express-rate-limit@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-6.4.0.tgz#b7066afe21157a012ed2b7c9adde386e712485cd" + integrity sha512-lxQRZI4gi3qAWTf0/Uqsyugsz57h8bd7QyllXBgJvd6DJKokzW7C5DTaNvwzvAQzwHGFaItybfYGhC8gpu0V2A== + +express@4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" + integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.0" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.10.3" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +ext@^1.1.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz#3871d50641e874cc172e2b53f919842d19db4c52" + integrity sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg== + dependencies: + type "^2.5.0" + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-csv@^4.3.1: + version "4.3.6" + resolved "https://registry.yarnpkg.com/fast-csv/-/fast-csv-4.3.6.tgz#70349bdd8fe4d66b1130d8c91820b64a21bc4a63" + integrity sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw== + dependencies: + "@fast-csv/format" "4.3.5" + "@fast-csv/parse" "4.3.6" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-equals@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-4.0.1.tgz#ff8f92d18f4f4130ce6fbd3748ef714d01cd0893" + integrity sha512-OXqyj3MD0p8Kee16Jz7CbCnXo+5CHKKu4xBh5UhC1NbmMkHn8WScLRy/B2q5UOlWMlNSQJc4mwXW30Lz+JUZJw== + +fast-glob@3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-glob@^3.2.7, fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@2.1.0, fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-safe-stringify@2.1.1, fast-safe-stringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + +fast-xml-parser@3.19.0: + version "3.19.0" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz#cb637ec3f3999f51406dd8ff0e6fc4d83e520d01" + integrity sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg== + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +fecha@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" + integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== + +figures@3.2.0, figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +file-stream-rotator@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz#007019e735b262bb6c6f0197e58e5c87cb96cec3" + integrity sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ== + dependencies: + moment "^2.29.1" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +filter-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" + integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-node-modules@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/find-node-modules/-/find-node-modules-2.1.3.tgz#3c976cff2ca29ee94b4f9eafc613987fc4c0ee44" + integrity sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg== + dependencies: + findup-sync "^4.0.0" + merge "^2.1.1" + +find-root@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== + dependencies: + locate-path "^2.0.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +findup-sync@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0" + integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^4.0.2" + resolve-dir "^1.0.1" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +flatted@^3.1.0: + version "3.2.5" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" + integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== + +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== + +follow-redirects@^1.14.9: + version "1.15.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== + +fork-ts-checker-webpack-plugin@7.2.11: + version "7.2.11" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.11.tgz#aff3febbc11544ba3ad0ae4d5aa4055bd15cd26d" + integrity sha512-2e5+NyTUTE1Xq4fWo7KFEQblCaIvvINQwUX3jRmEGlgCTc1Ecqw/975EfQrQ0GEraxJTnp8KB9d/c8hlCHUMJA== + dependencies: + "@babel/code-frame" "^7.16.7" + chalk "^4.1.2" + chokidar "^3.5.3" + cosmiconfig "^7.0.1" + deepmerge "^4.2.2" + fs-extra "^10.0.0" + memfs "^3.4.1" + minimatch "^3.0.4" + schema-utils "^3.1.1" + semver "^7.3.5" + tapable "^2.2.1" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +formidable@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.0.1.tgz#4310bc7965d185536f9565184dee74fbb75557ff" + integrity sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ== + dependencies: + dezalgo "1.0.3" + hexoid "1.0.0" + once "1.4.0" + qs "6.9.3" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-extra@10.1.0, fs-extra@^10.0.0, fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-minipass@^2.0.0, fs-minipass@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs-monkey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" + integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function.prototype.name@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" + integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + functions-have-names "^1.2.2" + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + +functions-have-names@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + +gensequence@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/gensequence/-/gensequence-3.1.1.tgz#95c1afc7c0680f92942c17f2d6f83f3d26ea97af" + integrity sha512-ys3h0hiteRwmY6BsvSttPmkhC0vEQHPJduANBRtH/dlDPZ0UBIb/dXy80IcckXyuQ6LKg+PloRqvGER9IS7F7g== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +geolib@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/geolib/-/geolib-3.3.3.tgz#17f5a0dcdc0b051bd631b66f7131d2c14c54a15b" + integrity sha512-YO704pzdB/8QQekQuDmFD5uv5RAwAf4rOUPdcMhdEOz+HoPWD0sC7Qqdwb+LAvwIjXVRawx0QgZlocKYh8PFOQ== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" + integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-pkg-repo@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz#75973e1c8050c73f48190c52047c4cee3acbf385" + integrity sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA== + dependencies: + "@hutson/parse-repository-url" "^3.0.0" + hosted-git-info "^4.0.0" + through2 "^2.0.0" + yargs "^16.2.0" + +get-port@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" + integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== + +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +get-stream@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +git-raw-commits@^2.0.8: + version "2.0.11" + resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.11.tgz#bc3576638071d18655e1cc60d7f524920008d723" + integrity sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A== + dependencies: + dargs "^7.0.0" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" + +git-remote-origin-url@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f" + integrity sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw== + dependencies: + gitconfiglocal "^1.0.0" + pify "^2.3.0" + +git-semver-tags@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-4.1.1.tgz#63191bcd809b0ec3e151ba4751c16c444e5b5780" + integrity sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA== + dependencies: + meow "^8.0.0" + semver "^6.0.0" + +git-up@^4.0.0: + version "4.0.5" + resolved "https://registry.yarnpkg.com/git-up/-/git-up-4.0.5.tgz#e7bb70981a37ea2fb8fe049669800a1f9a01d759" + integrity sha512-YUvVDg/vX3d0syBsk/CKUTib0srcQME0JyHkL5BaYdwLsiCslPWmDSi8PUMo9pXYjrryMcmsCoCgsTpSCJEQaA== + dependencies: + is-ssh "^1.3.0" + parse-url "^6.0.0" + +git-url-parse@^11.4.4: + version "11.6.0" + resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.6.0.tgz#c634b8de7faa66498a2b88932df31702c67df605" + integrity sha512-WWUxvJs5HsyHL6L08wOusa/IXYtMuCAhrMmnTjQPpBU0TTHyDhnOATNH3xNQz7YOQUsqIIPTGr4xiVti1Hsk5g== + dependencies: + git-up "^4.0.0" + +gitconfiglocal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b" + integrity sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ== + dependencies: + ini "^1.3.2" + +glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@7.1.4: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.0.0, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^8.0.1, glob@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" + integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +global-dirs@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" + integrity sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg== + dependencies: + ini "^1.3.4" + +global-modules@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" + integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== + dependencies: + global-prefix "^1.0.1" + is-windows "^1.0.1" + resolve-dir "^1.0.0" + +global-prefix@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" + integrity sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg== + dependencies: + expand-tilde "^2.0.2" + homedir-polyfill "^1.0.1" + ini "^1.3.4" + is-windows "^1.0.1" + which "^1.2.14" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.15.0: + version "13.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" + integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== + dependencies: + type-fest "^0.20.2" + +globby@^11.0.2, globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +handlebars@^4.7.7: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +hard-rejection@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" + integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-own-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-own-prop/-/has-own-prop-2.0.0.tgz#f0f95d58f65804f5d218db32563bb85b8e0417af" + integrity sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ== + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +helmet@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-5.1.0.tgz#e98a5d4bf89ab8119c856018a3bcc82addadcd47" + integrity sha512-klsunXs8rgNSZoaUrNeuCiWUxyc+wzucnEnFejUg3/A+CaF589k9qepLZZ1Jehnzig7YbD4hEuscGXuBY3fq+g== + +hexoid@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" + integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== + +homedir-polyfill@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + dependencies: + parse-passwd "^1.0.0" + +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" + integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== + dependencies: + lru-cache "^6.0.0" + +hosted-git-info@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-5.0.0.tgz#df7a06678b4ebd722139786303db80fdf302ea56" + integrity sha512-rRnjWu0Bxj+nIfUOkz0695C0H6tRrN5iYIzYejb0tDEefe2AekHu/U5Kn9pEie5vsJqpNQU02az7TGSH3qpz4Q== + dependencies: + lru-cache "^7.5.1" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-cache-semantics@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +husky@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.1.tgz#511cb3e57de3e3190514ae49ed50f6bc3f50b3e9" + integrity sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw== + +iconv-lite@0.4.24, iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore-walk@^3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" + integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== + dependencies: + minimatch "^3.0.4" + +ignore-walk@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" + integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== + dependencies: + minimatch "^5.0.1" + +ignore@^5.0.4, ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + +import-fresh@^3.0.0, import-fresh@^3.2.1, import-fresh@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@^1.3.2, ini@^1.3.4: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +init-package-json@^2.0.2: + version "2.0.5" + resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-2.0.5.tgz#78b85f3c36014db42d8f32117252504f68022646" + integrity sha512-u1uGAtEFu3VA6HNl/yUWw57jmKEMx8SKOxHhxjGnOFUiIlFnohKDFg4ZrPpv9wWqk44nDxGJAtqjdQFm+9XXQA== + dependencies: + npm-package-arg "^8.1.5" + promzard "^0.3.0" + read "~1.0.1" + read-package-json "^4.1.1" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + validate-npm-package-name "^3.0.0" + +inquirer@6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" + integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== + dependencies: + ansi-escapes "^3.2.0" + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^2.0.0" + lodash "^4.17.12" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.4.0" + string-width "^2.1.0" + strip-ansi "^5.1.0" + through "^2.3.6" + +inquirer@7.3.3, inquirer@^7.3.3: + version "7.3.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +inquirer@8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.0.tgz#f44f008dd344bbfc4b30031f45d984e034a3ac3a" + integrity sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.2.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +ip@^1.1.5: + version "1.1.8" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48" + integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-core-module@^2.5.0, is-core-module@^2.8.1, is-core-module@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" + integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== + +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +is-promise@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" + integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-ssh@^1.3.0: + version "1.3.3" + resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.3.tgz#7f133285ccd7f2c2c7fc897b771b53d95a2b2c7e" + integrity sha512-NKzJmQzJfEEma3w5cJNcUMxoXfDjz0Zj0eyCalHn2E6VOwlzjZo0yuO2fcBSf8zhFuVCL/82/r5gRcoi6aEPVQ== + dependencies: + protocols "^1.1.0" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-text-path@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" + integrity sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w== + dependencies: + text-extensions "^1.0.0" + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-utf8@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-windows@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" + integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.4" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" + integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +iterare@1.2.1, iterare@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" + integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== + +jest-changed-files@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-28.0.2.tgz#7d7810660a5bd043af9e9cfbe4d58adb05e91531" + integrity sha512-QX9u+5I2s54ZnGoMEjiM2WeBvJR2J7w/8ZUmH2um/WLAuGAYFQcsVXY9+1YL6k0H/AGUdH8pXUAv6erDqEsvIA== + dependencies: + execa "^5.0.0" + throat "^6.0.1" + +jest-circus@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-28.1.1.tgz#3d27da6a974d85a466dc0cdc6ddeb58daaa57bb4" + integrity sha512-75+BBVTsL4+p2w198DQpCeyh1RdaS2lhEG87HkaFX/UG0gJExVq2skG2pT7XZEGBubNj2CytcWSPan4QEPNosw== + dependencies: + "@jest/environment" "^28.1.1" + "@jest/expect" "^28.1.1" + "@jest/test-result" "^28.1.1" + "@jest/types" "^28.1.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + is-generator-fn "^2.0.0" + jest-each "^28.1.1" + jest-matcher-utils "^28.1.1" + jest-message-util "^28.1.1" + jest-runtime "^28.1.1" + jest-snapshot "^28.1.1" + jest-util "^28.1.1" + pretty-format "^28.1.1" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + +jest-cli@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-28.1.1.tgz#23ddfde8940e1818585ae4a568877b33b0e51cfe" + integrity sha512-+sUfVbJqb1OjBZ0OdBbI6OWfYM1i7bSfzYy6gze1F1w3OKWq8ZTEKkZ8a7ZQPq6G/G1qMh/uKqpdWhgl11NFQQ== + dependencies: + "@jest/core" "^28.1.1" + "@jest/test-result" "^28.1.1" + "@jest/types" "^28.1.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^28.1.1" + jest-util "^28.1.1" + jest-validate "^28.1.1" + prompts "^2.0.1" + yargs "^17.3.1" + +jest-config@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-28.1.1.tgz#e90b97b984f14a6c24a221859e81b258990fce2f" + integrity sha512-tASynMhS+jVV85zKvjfbJ8nUyJS/jUSYZ5KQxLUN2ZCvcQc/OmhQl2j6VEL3ezQkNofxn5pQ3SPYWPHb0unTZA== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^28.1.1" + "@jest/types" "^28.1.1" + babel-jest "^28.1.1" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^28.1.1" + jest-environment-node "^28.1.1" + jest-get-type "^28.0.2" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.1" + jest-runner "^28.1.1" + jest-util "^28.1.1" + jest-validate "^28.1.1" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^28.1.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-28.1.1.tgz#1a3eedfd81ae79810931c63a1d0f201b9120106c" + integrity sha512-/MUUxeR2fHbqHoMMiffe/Afm+U8U4olFRJ0hiVG2lZatPJcnGxx292ustVu7bULhjV65IYMxRdploAKLbcrsyg== + dependencies: + chalk "^4.0.0" + diff-sequences "^28.1.1" + jest-get-type "^28.0.2" + pretty-format "^28.1.1" + +jest-docblock@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8" + integrity sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA== + dependencies: + detect-newline "^3.0.0" + +jest-each@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-28.1.1.tgz#ba5238dacf4f31d9fe23ddc2c44c01e7c23885c4" + integrity sha512-A042rqh17ZvEhRceDMi784ppoXR7MWGDEKTXEZXb4svt0eShMZvijGxzKsx+yIjeE8QYmHPrnHiTSQVhN4nqaw== + dependencies: + "@jest/types" "^28.1.1" + chalk "^4.0.0" + jest-get-type "^28.0.2" + jest-util "^28.1.1" + pretty-format "^28.1.1" + +jest-environment-node@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-28.1.1.tgz#1c86c59003a7d319fa06ea3b1bbda6c193715c67" + integrity sha512-2aV/eeY/WNgUUJrrkDJ3cFEigjC5fqT1+fCclrY6paqJ5zVPoM//sHmfgUUp7WLYxIdbPwMiVIzejpN56MxnNA== + dependencies: + "@jest/environment" "^28.1.1" + "@jest/fake-timers" "^28.1.1" + "@jest/types" "^28.1.1" + "@types/node" "*" + jest-mock "^28.1.1" + jest-util "^28.1.1" + +jest-get-type@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203" + integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== + +jest-haste-map@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.1.tgz#471685f1acd365a9394745bb97c8fc16289adca3" + integrity sha512-ZrRSE2o3Ezh7sb1KmeLEZRZ4mgufbrMwolcFHNRSjKZhpLa8TdooXOOFlSwoUzlbVs1t0l7upVRW2K7RWGHzbQ== + dependencies: + "@jest/types" "^28.1.1" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^28.0.2" + jest-util "^28.1.1" + jest-worker "^28.1.1" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.1.tgz#537f37afd610a4b3f4cab15e06baf60484548efb" + integrity sha512-4jvs8V8kLbAaotE+wFR7vfUGf603cwYtFf1/PYEsyX2BAjSzj8hQSVTP6OWzseTl0xL6dyHuKs2JAks7Pfubmw== + dependencies: + jest-get-type "^28.0.2" + pretty-format "^28.1.1" + +jest-matcher-utils@^28.0.0, jest-matcher-utils@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-28.1.1.tgz#a7c4653c2b782ec96796eb3088060720f1e29304" + integrity sha512-NPJPRWrbmR2nAJ+1nmnfcKKzSwgfaciCCrYZzVnNoxVoyusYWIjkBMNvu0RHJe7dNj4hH3uZOPZsQA+xAYWqsw== + dependencies: + chalk "^4.0.0" + jest-diff "^28.1.1" + jest-get-type "^28.0.2" + pretty-format "^28.1.1" + +jest-message-util@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.1.tgz#60aa0b475cfc08c8a9363ed2fb9108514dd9ab89" + integrity sha512-xoDOOT66fLfmTRiqkoLIU7v42mal/SqwDKvfmfiWAdJMSJiU+ozgluO7KbvoAgiwIrrGZsV7viETjc8GNrA/IQ== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^28.1.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^28.1.1" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-28.1.1.tgz#37903d269427fa1ef5b2447be874e1c62a39a371" + integrity sha512-bDCb0FjfsmKweAvE09dZT59IMkzgN0fYBH6t5S45NoJfd2DHkS3ySG2K+hucortryhO3fVuXdlxWcbtIuV/Skw== + dependencies: + "@jest/types" "^28.1.1" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" + integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== + +jest-resolve-dependencies@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.1.tgz#3dffaaa56f4b41bc6b61053899d1756401763a27" + integrity sha512-p8Y150xYJth4EXhOuB8FzmS9r8IGLEioiaetgdNGb9VHka4fl0zqWlVe4v7mSkYOuEUg2uB61iE+zySDgrOmgQ== + dependencies: + jest-regex-util "^28.0.2" + jest-snapshot "^28.1.1" + +jest-resolve@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-28.1.1.tgz#bc2eaf384abdcc1aaf3ba7c50d1adf01e59095e5" + integrity sha512-/d1UbyUkf9nvsgdBildLe6LAD4DalgkgZcKd0nZ8XUGPyA/7fsnaQIlKVnDiuUXv/IeZhPEDrRJubVSulxrShA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.1" + jest-pnp-resolver "^1.2.2" + jest-util "^28.1.1" + jest-validate "^28.1.1" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-28.1.1.tgz#9ecdb3f27a00059986797aa6b012ba8306aa436c" + integrity sha512-W5oFUiDBgTsCloTAj6q95wEvYDB0pxIhY6bc5F26OucnwBN+K58xGTGbliSMI4ChQal5eANDF+xvELaYkJxTmA== + dependencies: + "@jest/console" "^28.1.1" + "@jest/environment" "^28.1.1" + "@jest/test-result" "^28.1.1" + "@jest/transform" "^28.1.1" + "@jest/types" "^28.1.1" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.10.2" + graceful-fs "^4.2.9" + jest-docblock "^28.1.1" + jest-environment-node "^28.1.1" + jest-haste-map "^28.1.1" + jest-leak-detector "^28.1.1" + jest-message-util "^28.1.1" + jest-resolve "^28.1.1" + jest-runtime "^28.1.1" + jest-util "^28.1.1" + jest-watcher "^28.1.1" + jest-worker "^28.1.1" + source-map-support "0.5.13" + throat "^6.0.1" + +jest-runtime@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-28.1.1.tgz#569e1dc3c36c6c4c0b29516c1c49b6ad580abdaf" + integrity sha512-J89qEJWW0leOsqyi0D9zHpFEYHwwafFdS9xgvhFHtIdRghbadodI0eA+DrthK/1PebBv3Px8mFSMGKrtaVnleg== + dependencies: + "@jest/environment" "^28.1.1" + "@jest/fake-timers" "^28.1.1" + "@jest/globals" "^28.1.1" + "@jest/source-map" "^28.0.2" + "@jest/test-result" "^28.1.1" + "@jest/transform" "^28.1.1" + "@jest/types" "^28.1.1" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.1" + jest-message-util "^28.1.1" + jest-mock "^28.1.1" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.1" + jest-snapshot "^28.1.1" + jest-util "^28.1.1" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-28.1.1.tgz#ab825c16c8d8b5e883bd57eee6ca8748c42ab848" + integrity sha512-1KjqHJ98adRcbIdMizjF5DipwZFbvxym/kFO4g4fVZCZRxH/dqV8TiBFCa6rqic3p0karsy8RWS1y4E07b7P0A== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^28.1.1" + "@jest/transform" "^28.1.1" + "@jest/types" "^28.1.1" + "@types/babel__traverse" "^7.0.6" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^28.1.1" + graceful-fs "^4.2.9" + jest-diff "^28.1.1" + jest-get-type "^28.0.2" + jest-haste-map "^28.1.1" + jest-matcher-utils "^28.1.1" + jest-message-util "^28.1.1" + jest-util "^28.1.1" + natural-compare "^1.4.0" + pretty-format "^28.1.1" + semver "^7.3.5" + +jest-util@^28.0.0, jest-util@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.1.tgz#ff39e436a1aca397c0ab998db5a51ae2b7080d05" + integrity sha512-FktOu7ca1DZSyhPAxgxB6hfh2+9zMoJ7aEQA759Z6p45NuO8mWcqujH+UdHlCm/V6JTWwDztM2ITCzU1ijJAfw== + dependencies: + "@jest/types" "^28.1.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-28.1.1.tgz#59b7b339b3c85b5144bd0c06ad3600f503a4acc8" + integrity sha512-Kpf6gcClqFCIZ4ti5++XemYJWUPCFUW+N2gknn+KgnDf549iLul3cBuKVe1YcWRlaF8tZV8eJCap0eECOEE3Ug== + dependencies: + "@jest/types" "^28.1.1" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^28.0.2" + leven "^3.1.0" + pretty-format "^28.1.1" + +jest-watcher@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-28.1.1.tgz#533597fb3bfefd52b5cd115cd916cffd237fb60c" + integrity sha512-RQIpeZ8EIJMxbQrXpJQYIIlubBnB9imEHsxxE41f54ZwcqWLysL/A0ZcdMirf+XsMn3xfphVQVV4EW0/p7i7Ug== + dependencies: + "@jest/test-result" "^28.1.1" + "@jest/types" "^28.1.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.10.2" + jest-util "^28.1.1" + string-length "^4.0.1" + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest-worker@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.1.tgz#3480c73247171dfd01eda77200f0063ab6a3bf28" + integrity sha512-Au7slXB08C6h+xbJPp7VIb6U0XX5Kc9uel/WFc6/rcTzGiaVCBRngBExSYuXSLFPULPSYU3cJ3ybS988lNFQhQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^28.1.0, jest@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-28.1.1.tgz#3c39a3a09791e16e9ef283597d24ab19a0df701e" + integrity sha512-qw9YHBnjt6TCbIDMPMpJZqf9E12rh6869iZaN08/vpOGgHJSAaLLUn6H8W3IAEuy34Ls3rct064mZLETkxJ2XA== + dependencies: + "@jest/core" "^28.1.1" + "@jest/types" "^28.1.1" + import-local "^3.0.2" + jest-cli "^28.1.1" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@4.1.0, js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json-stringify-nice@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" + integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.3, json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + +jsonc-parser@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" + integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonparse@^1.2.0, jsonparse@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== + +jsonwebtoken@8.5.1, jsonwebtoken@^8.2.0: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + +jszip@^3.5.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.0.tgz#faf3db2b4b8515425e34effcdbb086750a346061" + integrity sha512-LDfVtOLtOxb9RXkYOwPyNBTQDL4eUbqahtoY6x07GiDJHwSYvn8sHHIw8wINImV3MqbMNve2gSuM1DDqEKk09Q== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + +just-diff-apply@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/just-diff-apply/-/just-diff-apply-5.3.1.tgz#30f40809ffed55ad76dccf73fa9b85a76964c867" + integrity sha512-dgFenZnMsc1xGNqgdtgnh7DK+Oy352CE3VZLbzcbQpsBs9iI2K3M0IRrdgREZ72eItTjbl0suRyvKRdVQa9GbA== + +just-diff@^5.0.1: + version "5.0.3" + resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-5.0.3.tgz#4c9c514dec5526b25ab977590e3c39a0cf271554" + integrity sha512-a8p80xcpJ6sdurk5PxDKb4mav9MeKjA3zFKZpCWBIfvg8mznfnmb13MKZvlrwJ+Lhis0wM3uGAzE0ArhFHvIcg== + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +kareem@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.4.1.tgz#7d81ec518204a48c1cb16554af126806c3cd82b0" + integrity sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA== + +kind-of@^6.0.2, kind-of@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== + +lazystream@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" + integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== + dependencies: + readable-stream "^2.0.5" + +lerna@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/lerna/-/lerna-5.1.4.tgz#57b1cd1fc0078f3c5009a07a7f7a4f8b85929b70" + integrity sha512-WwSbMslPxWSV7ARsGzkhJAFC1uQcuNGgiy2vZho4bpXVC+A7ZLXy8FngDbcAn7hCGC3ZDnl/4jdY6d84j63Y4g== + dependencies: + "@lerna/add" "5.1.4" + "@lerna/bootstrap" "5.1.4" + "@lerna/changed" "5.1.4" + "@lerna/clean" "5.1.4" + "@lerna/cli" "5.1.4" + "@lerna/create" "5.1.4" + "@lerna/diff" "5.1.4" + "@lerna/exec" "5.1.4" + "@lerna/import" "5.1.4" + "@lerna/info" "5.1.4" + "@lerna/init" "5.1.4" + "@lerna/link" "5.1.4" + "@lerna/list" "5.1.4" + "@lerna/publish" "5.1.4" + "@lerna/run" "5.1.4" + "@lerna/version" "5.1.4" + import-local "^3.0.2" + npmlog "^6.0.2" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +libnpmaccess@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-4.0.3.tgz#dfb0e5b0a53c315a2610d300e46b4ddeb66e7eec" + integrity sha512-sPeTSNImksm8O2b6/pf3ikv4N567ERYEpeKRPSmqlNt1dTZbvgpJIzg5vAhXHpw2ISBsELFRelk0jEahj1c6nQ== + dependencies: + aproba "^2.0.0" + minipass "^3.1.1" + npm-package-arg "^8.1.2" + npm-registry-fetch "^11.0.0" + +libnpmpublish@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-4.0.2.tgz#be77e8bf5956131bcb45e3caa6b96a842dec0794" + integrity sha512-+AD7A2zbVeGRCFI2aO//oUmapCwy7GHqPXFJh3qpToSRNU+tXKJ2YFUgjt04LPPAf2dlEH95s6EhIHM1J7bmOw== + dependencies: + normalize-package-data "^3.0.2" + npm-package-arg "^8.1.2" + npm-registry-fetch "^11.0.0" + semver "^7.1.3" + ssri "^8.0.1" + +libphonenumber-js@^1.9.43: + version "1.10.7" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.7.tgz#4c010b7b57e824c571ea4cdbf7aea6f3c408878c" + integrity sha512-jZXLCCWMe1b/HXkjiLeYt2JsytZMcqH26jLFIdzFDFF0xvSUWrYKyvPlyPG+XJzEyKUFbcZxLdWGMwQsWaHDxQ== + +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ== + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +load-json-file@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" + integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ== + dependencies: + graceful-fs "^4.1.15" + parse-json "^5.0.0" + strip-bom "^4.0.0" + type-fest "^0.6.0" + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA== + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + +lodash.compact@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.compact/-/lodash.compact-3.0.1.tgz#540ce3837745975807471e16b4a2ba21e7256ca5" + integrity sha512-2ozeiPi+5eBXW1CLtzjk8XQFhQOEMwwfxblqeq6EGyTxZJ1bPATqilY0e6g2SLQpP4KuMeuioBhEnWz5Pr7ICQ== + +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA== + +lodash.escaperegexp@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" + integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw== + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ== + +lodash.groupby@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" + integrity sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw== + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + +lodash.isfunction@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" + integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.ismatch@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" + integrity sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g== + +lodash.isnil@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz#49e28cd559013458c814c5479d3c663a21bfaa6c" + integrity sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.isundefined@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48" + integrity sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA== + +lodash.map@^4.5.1: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" + integrity sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q== + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + +lodash.union@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" + integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== + +lodash@4.17.21, lodash@^4.17.12, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +logform@^2.3.2, logform@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.4.1.tgz#512c9eaef738044d1c619790ba0f806c80d9d3a9" + integrity sha512-7XB/tqc3VRbri9pRjU6E97mQ8vC27ivJ3lct4jhyT+n0JNDd4YKldFl0D75NqDp46hk8RC7Ma1Vjv/UPf67S+A== + dependencies: + "@colors/colors" "1.5.0" + fecha "^4.2.0" + ms "^2.1.1" + safe-stable-stringify "^2.3.1" + triple-beam "^1.3.0" + +longest@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-2.0.1.tgz#781e183296aa94f6d4d916dc335d0d17aefa23f8" + integrity sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lru-cache@^7.10.1, lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: + version "7.10.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.10.1.tgz#db577f42a94c168f676b638d15da8fb073448cab" + integrity sha512-BQuhQxPuRl79J5zSXRP+uNzPOyZw2oFI9JLRQ80XswSvg21KMKNtQza9eF42rfI/3Z40RvzBdXgziEkudzjo8A== + +lru-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" + integrity sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ== + dependencies: + es5-ext "~0.10.2" + +luxon@^1.23.x: + version "1.28.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" + integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ== + +macos-release@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.0.tgz#067c2c88b5f3fb3c56a375b2ec93826220fa1ff2" + integrity sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g== + +magic-string@0.25.7: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + dependencies: + sourcemap-codec "^1.4.4" + +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-dir@^3.0.0, make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-error@1.x, make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +make-fetch-happen@^10.0.3, make-fetch-happen@^10.0.6: + version "10.1.8" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.1.8.tgz#3b6e93dd8d8fdb76c0d7bf32e617f37c3108435a" + integrity sha512-0ASJbG12Au6+N5I84W+8FhGS6iM8MyzvZady+zaQAu+6IOaESFzCLLD0AR1sAFF3Jufi8bxm586ABN6hWd3k7g== + dependencies: + agentkeepalive "^4.2.1" + cacache "^16.1.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-fetch "^2.0.3" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.3" + promise-retry "^2.0.1" + socks-proxy-agent "^7.0.0" + ssri "^9.0.0" + +make-fetch-happen@^8.0.9: + version "8.0.14" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz#aaba73ae0ab5586ad8eaa68bd83332669393e222" + integrity sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.0.5" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + promise-retry "^2.0.1" + socks-proxy-agent "^5.0.0" + ssri "^8.0.0" + +make-fetch-happen@^9.0.1, make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^6.0.0" + ssri "^8.0.0" + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== + +map-obj@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" + integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memfs@^3.4.1: + version "3.4.6" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.6.tgz#74097983d27c82b973665885dc75f27a65174510" + integrity sha512-rH9mjopto6Wkr7RFuH9l9dk3qb2XGOcYKr7xMhaYqfzuJqOqhRrcFvfD7JMuPj6SLmPreh5+6eAuv36NFAU+Mw== + dependencies: + fs-monkey "^1.0.3" + +memoizee@^0.4.15: + version "0.4.15" + resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72" + integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== + dependencies: + d "^1.0.1" + es5-ext "^0.10.53" + es6-weak-map "^2.0.3" + event-emitter "^0.3.5" + is-promise "^2.2.2" + lru-queue "^0.1.0" + next-tick "^1.1.0" + timers-ext "^0.1.7" + +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + +meow@^8.0.0: + version "8.1.2" + resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" + integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.18.0" + yargs-parser "^20.2.3" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +merge@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" + integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== + +methods@^1.1.2, methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mime@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +minimatch@3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.5.tgz#4da8f1290ee0f0f8e83d60ca69f8f134068604a3" + integrity sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" + integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + dependencies: + brace-expansion "^2.0.1" + +minimist-options@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" + integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" + +minimist@1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +minimist@1.2.6, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^1.3.0, minipass-fetch@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" + integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + +minipass-fetch@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.0.tgz#ca1754a5f857a3be99a9271277246ac0b44c3ff8" + integrity sha512-H9U4UVBGXEyyWJnqYDCLp1PwD8XIkJ4akNHp1aGVI+2Ym7wQMlxDKi4IB4JbmyU+pl9pEs/cVrK6cOuvmbK4Sg== + dependencies: + minipass "^3.1.6" + minipass-sized "^1.0.3" + minizlib "^2.1.2" + optionalDependencies: + encoding "^0.1.13" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-json-stream@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz#7edbb92588fbfc2ff1db2fc10397acb7b6b44aa7" + integrity sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg== + dependencies: + jsonparse "^1.3.1" + minipass "^3.0.0" + +minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3, minipass@^3.1.6: + version "3.3.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.3.tgz#fd1f0e6c06449c10dadda72618b59c00f3d6378d" + integrity sha512-N0BOsdFAlNRfmwMhjAsLVWOk7Ljmeb39iqFlsV1At+jqRhSUP9yeof8FyJu4imaJiSUp8vQebWD/guZwGQC8iA== + dependencies: + yallist "^4.0.0" + +minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp-infer-owner@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" + integrity sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw== + dependencies: + chownr "^2.0.0" + infer-owner "^1.0.4" + mkdirp "^1.0.3" + +"mkdirp@>=0.5 0", mkdirp@^0.5.4: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +modify-values@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" + integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== + +moment-timezone@^0.5.34: + version "0.5.34" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c" + integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg== + dependencies: + moment ">= 2.9.0" + +"moment@>= 2.9.0", moment@^2.29.1, moment@^2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" + integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== + +mongodb-connection-string-url@^2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz#f075c8d529e8d3916386018b8a396aed4f16e5ed" + integrity sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA== + dependencies: + "@types/whatwg-url" "^8.2.1" + whatwg-url "^11.0.0" + +mongodb@4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.7.0.tgz#99f7323271d93659067695b60e7b4efee2de9bf0" + integrity sha512-HhVar6hsUeMAVlIbwQwWtV36iyjKd9qdhY+s4wcU8K6TOj4Q331iiMy+FoPuxEntDIijTYWivwFJkLv8q/ZgvA== + dependencies: + bson "^4.6.3" + denque "^2.0.1" + mongodb-connection-string-url "^2.5.2" + socks "^2.6.2" + optionalDependencies: + saslprep "^1.0.3" + +mongoose@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-6.4.0.tgz#22d6ab436bdf444b833e92faaa53e47b734c15c6" + integrity sha512-eBDrueap1Zx3qFrcYylTiqTFlL5iTEaYAxoDF1MSRdipwAzChQRMJve+vxHtxPhI2q5tmf9RYHfZwXfTUHPd3g== + dependencies: + bson "^4.6.2" + kareem "2.4.1" + mongodb "4.7.0" + mpath "0.9.0" + mquery "4.0.3" + ms "2.1.3" + sift "16.0.0" + +morgan@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7" + integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ== + dependencies: + basic-auth "~2.0.1" + debug "2.6.9" + depd "~2.0.0" + on-finished "~2.3.0" + on-headers "~1.0.2" + +mpath@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.9.0.tgz#0c122fe107846e31fc58c75b09c35514b3871904" + integrity sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew== + +mquery@4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/mquery/-/mquery-4.0.3.tgz#4d15f938e6247d773a942c912d9748bd1965f89d" + integrity sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA== + dependencies: + debug "4.x" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3, ms@^2.0.0, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multer@1.4.4-lts.1: + version "1.4.4-lts.1" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.4-lts.1.tgz#24100f701a4611211cfae94ae16ea39bb314e04d" + integrity sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg== + dependencies: + append-field "^1.0.0" + busboy "^1.0.0" + concat-stream "^1.5.2" + mkdirp "^0.5.4" + object-assign "^4.1.1" + type-is "^1.6.4" + xtend "^4.0.0" + +multimatch@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" + integrity sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA== + dependencies: + "@types/minimatch" "^3.0.3" + array-differ "^3.0.0" + array-union "^2.1.0" + arrify "^2.0.1" + minimatch "^3.0.4" + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ== + +mute-stream@0.0.8, mute-stream@~0.0.4: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +negotiator@0.6.3, negotiator@^0.6.2, negotiator@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.0, neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +nest-winston@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/nest-winston/-/nest-winston-1.6.2.tgz#e4f24096b7e5cd9c8649b3d15203e576114d07e0" + integrity sha512-pM3o4zttpkKdnpNXyvMKKBD2u7OWROmVglyWZpH7O7QOLAegbMPvyqWgPNfvw2kkla+T/H4HFgGqcZGqiTzI5Q== + dependencies: + cli-color "^2.0.1" + fast-safe-stringify "^2.1.1" + +nestjs-command@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/nestjs-command/-/nestjs-command-3.1.1.tgz#26e11cef4b3f588a63d20fdf32da7e18e61d774a" + integrity sha512-jis8nS0QNZkw35BzBlkkOqty4BJcomUZdmIyne+HjSdk0CwIy6PzXkhGiUIPySBwoK/6d4OxjFQckVoNB/H0TA== + dependencies: + lodash.compact "^3.0.1" + lodash.flattendeep "^4.4.0" + +nestjs-i18n@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/nestjs-i18n/-/nestjs-i18n-9.1.2.tgz#f0e0c79c0400fb7fe84a1602d3f2c79e998c8546" + integrity sha512-5DDmhmjdslK1BBav7czfWVGV15WLDmyRBBMUCzvirGgWQAGVcoEm0LoV7iKIAOBzZAHHlRJVuxARvNXvZtXtBA== + dependencies: + accept-language-parser "^1.5.0" + chokidar "^3.5.3" + cookie "^0.4.1" + iterare "^1.2.1" + js-yaml "^4.1.0" + string-format "^2.0.0" + +next-tick@1, next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +node-addon-api@^3.1.0, node-addon-api@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" + integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== + +node-emoji@1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" + integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== + dependencies: + lodash "^4.17.21" + +node-fetch@^2.6.1, node-fetch@^2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +node-gyp-build@^4.3.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.4.0.tgz#42e99687ce87ddeaf3a10b99dc06abc11021f3f4" + integrity sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ== + +node-gyp@^8.4.1: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + +node-gyp@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.0.0.tgz#e1da2067427f3eb5bb56820cb62bc6b1e4bd2089" + integrity sha512-Ma6p4s+XCTPxCuAMrOA/IJRmVy16R8Sdhtwl4PrCr7IBlj4cPawF0vg/l7nOT1jPbuNS7lIRJpBSvVsXwEZuzw== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^10.0.3" + nopt "^5.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" + integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^3.0.0, normalize-package-data@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" + integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== + dependencies: + hosted-git-info "^4.0.1" + is-core-module "^2.5.0" + semver "^7.3.4" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-4.0.0.tgz#1122d5359af21d4cd08718b92b058a658594177c" + integrity sha512-m+GL22VXJKkKbw62ZaBBjv8u6IE3UI4Mh5QakIqs3fWiKe0Xyi6L97hakwZK41/LD4R/2ly71Bayx0NLMwLA/g== + dependencies: + hosted-git-info "^5.0.0" + is-core-module "^2.8.1" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-url@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +npm-bundled@^1.1.1, npm-bundled@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" + integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== + dependencies: + npm-normalize-package-bin "^1.0.1" + +npm-install-checks@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-5.0.0.tgz#5ff27d209a4e3542b8ac6b0c1db6063506248234" + integrity sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA== + dependencies: + semver "^7.1.1" + +npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== + +npm-package-arg@^8.0.0, npm-package-arg@^8.1.0, npm-package-arg@^8.1.2, npm-package-arg@^8.1.5: + version "8.1.5" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.5.tgz#3369b2d5fe8fdc674baa7f1786514ddc15466e44" + integrity sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q== + dependencies: + hosted-git-info "^4.0.1" + semver "^7.3.4" + validate-npm-package-name "^3.0.0" + +npm-package-arg@^9.0.0, npm-package-arg@^9.0.1: + version "9.0.2" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-9.0.2.tgz#f3ef7b1b3b02e82564af2d5228b4c36567dcd389" + integrity sha512-v/miORuX8cndiOheW8p2moNuPJ7QhcFh9WGlTorruG8hXSA23vMTEp5hTCmDxic0nD8KHhj/NQgFuySD3GYY3g== + dependencies: + hosted-git-info "^5.0.0" + semver "^7.3.5" + validate-npm-package-name "^4.0.0" + +npm-packlist@^2.1.4: + version "2.2.2" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-2.2.2.tgz#076b97293fa620f632833186a7a8f65aaa6148c8" + integrity sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg== + dependencies: + glob "^7.1.6" + ignore-walk "^3.0.3" + npm-bundled "^1.1.1" + npm-normalize-package-bin "^1.0.1" + +npm-packlist@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.0.tgz#f3fd52903a021009913a133732022132eb355ce7" + integrity sha512-a04sqF6FbkyOAFA19AA0e94gS7Et5T2/IMj3VOT9nOF2RaRdVPQ1Q17Fb/HaDRFs+gbC7HOmhVZ29adpWgmDZg== + dependencies: + glob "^8.0.1" + ignore-walk "^5.0.1" + npm-bundled "^1.1.2" + npm-normalize-package-bin "^1.0.1" + +npm-pick-manifest@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-7.0.1.tgz#76dda30a7cd6b99be822217a935c2f5eacdaca4c" + integrity sha512-IA8+tuv8KujbsbLQvselW2XQgmXWS47t3CB0ZrzsRZ82DbDfkcFunOaPm4X7qNuhMfq+FmV7hQT4iFVpHqV7mg== + dependencies: + npm-install-checks "^5.0.0" + npm-normalize-package-bin "^1.0.1" + npm-package-arg "^9.0.0" + semver "^7.3.5" + +npm-registry-fetch@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz#68c1bb810c46542760d62a6a965f85a702d43a76" + integrity sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA== + dependencies: + make-fetch-happen "^9.0.1" + minipass "^3.1.3" + minipass-fetch "^1.3.0" + minipass-json-stream "^1.0.1" + minizlib "^2.0.0" + npm-package-arg "^8.0.0" + +npm-registry-fetch@^13.0.0, npm-registry-fetch@^13.0.1: + version "13.1.1" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-13.1.1.tgz#26dc4b26d0a545886e807748032ba2aefaaae96b" + integrity sha512-5p8rwe6wQPLJ8dMqeTnA57Dp9Ox6GH9H60xkyJup07FmVlu3Mk7pf/kIIpl9gaN5bM8NM+UUx3emUWvDNTt39w== + dependencies: + make-fetch-happen "^10.0.6" + minipass "^3.1.6" + minipass-fetch "^2.0.3" + minipass-json-stream "^1.0.1" + minizlib "^2.1.2" + npm-package-arg "^9.0.1" + proc-log "^2.0.0" + +npm-registry-fetch@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-9.0.0.tgz#86f3feb4ce00313bc0b8f1f8f69daae6face1661" + integrity sha512-PuFYYtnQ8IyVl6ib9d3PepeehcUeHN9IO5N/iCRhyg9tStQcqGQBRVHmfmMWPDERU3KwZoHFvbJ4FPXPspvzbA== + dependencies: + "@npmcli/ci-detect" "^1.0.0" + lru-cache "^6.0.0" + make-fetch-happen "^8.0.9" + minipass "^3.1.3" + minipass-fetch "^1.3.0" + minipass-json-stream "^1.0.1" + minizlib "^2.0.0" + npm-package-arg "^8.0.0" + +npm-run-path@^4.0.0, npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + +npmlog@^6.0.0, npmlog@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + +nx@14.3.6, nx@^14.3.6: + version "14.3.6" + resolved "https://registry.yarnpkg.com/nx/-/nx-14.3.6.tgz#0b30dbe96df2cd62cb68c9d3609e2dc3219b0a20" + integrity sha512-jBgqXEkRalo8PwXJzO4HVNN3P5pqyL5VawyNd34qPl+4t2XlDRqoK0J74i8liPGdKPBB1HjM2K1u+QNF2ROvQA== + dependencies: + "@nrwl/cli" "14.3.6" + "@nrwl/tao" "14.3.6" + "@parcel/watcher" "2.0.4" + chalk "4.1.0" + chokidar "^3.5.1" + cli-cursor "3.1.0" + cli-spinners "2.6.1" + cliui "^7.0.2" + dotenv "~10.0.0" + enquirer "~2.3.6" + fast-glob "3.2.7" + figures "3.2.0" + flat "^5.0.2" + fs-extra "^10.1.0" + glob "7.1.4" + ignore "^5.0.4" + js-yaml "4.1.0" + jsonc-parser "3.0.0" + minimatch "3.0.5" + npm-run-path "^4.0.1" + open "^8.4.0" + semver "7.3.4" + string-width "^4.2.3" + tar-stream "~2.2.0" + tmp "~0.2.1" + tsconfig-paths "^3.9.0" + tslib "^2.3.0" + v8-compile-cache "2.3.0" + yargs "^17.4.0" + yargs-parser "21.0.1" + +object-assign@^4, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-hash@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + +object-hash@^2.0.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + +object-inspect@^1.12.0, object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.values@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" + integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.1, on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@1.4.0, once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== + dependencies: + mimic-fn "^1.0.0" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +ora@5.4.1, ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +os-name@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/os-name/-/os-name-4.0.1.tgz#32cee7823de85a8897647ba4d76db46bf845e555" + integrity sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw== + dependencies: + macos-release "^2.5.0" + windows-release "^4.0.0" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg== + dependencies: + p-limit "^1.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map-series@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-2.1.0.tgz#7560d4c452d9da0c07e692fdbfe6e2c81a2a91f2" + integrity sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q== + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-pipe@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" + integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== + +p-queue@^6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + +p-reduce@^2.0.0, p-reduce@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-2.1.0.tgz#09408da49507c6c274faa31f28df334bc712b64a" + integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw== + +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +p-waterfall@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-waterfall/-/p-waterfall-2.1.1.tgz#63153a774f472ccdc4eb281cdb2967fcf158b2ee" + integrity sha512-RRTnDb2TBG/epPRI2yYXsimO0v3BXC8Yd3ogr1545IaqKK17VGhbWVeGGN+XfCm/08OK8635nH31c8bATkHuSw== + dependencies: + p-reduce "^2.0.0" + +pacote@^13.0.3, pacote@^13.0.5, pacote@^13.4.1: + version "13.6.1" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-13.6.1.tgz#ac6cbd9032b4c16e5c1e0c60138dfe44e4cc589d" + integrity sha512-L+2BI1ougAPsFjXRyBhcKmfT016NscRFLv6Pz5EiNf1CCFJFU0pSKKQwsZTyAQB+sTuUL4TyFyp6J1Ork3dOqw== + dependencies: + "@npmcli/git" "^3.0.0" + "@npmcli/installed-package-contents" "^1.0.7" + "@npmcli/promise-spawn" "^3.0.0" + "@npmcli/run-script" "^4.1.0" + cacache "^16.0.0" + chownr "^2.0.0" + fs-minipass "^2.1.0" + infer-owner "^1.0.4" + minipass "^3.1.6" + mkdirp "^1.0.4" + npm-package-arg "^9.0.0" + npm-packlist "^5.1.0" + npm-pick-manifest "^7.0.0" + npm-registry-fetch "^13.0.1" + proc-log "^2.0.0" + promise-retry "^2.0.1" + read-package-json "^5.0.0" + read-package-json-fast "^2.0.3" + rimraf "^3.0.2" + ssri "^9.0.0" + tar "^6.1.11" + +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parent-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-2.0.0.tgz#fa71f88ff1a50c27e15d8ff74e0e3a9523bf8708" + integrity sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg== + dependencies: + callsites "^3.1.0" + +parse-conflict-json@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/parse-conflict-json/-/parse-conflict-json-2.0.2.tgz#3d05bc8ffe07d39600dc6436c6aefe382033d323" + integrity sha512-jDbRGb00TAPFsKWCpZZOT93SxVP9nONOSgES3AevqRq/CHvavEBvKAjxX9p5Y5F0RZLxH9Ufd9+RwtCsa+lFDA== + dependencies: + json-parse-even-better-errors "^2.3.1" + just-diff "^5.0.1" + just-diff-apply "^5.2.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-json@^5.0.0, parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== + +parse-path@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.4.tgz#4bf424e6b743fb080831f03b536af9fc43f0ffea" + integrity sha512-Z2lWUis7jlmXC1jeOG9giRO2+FsuyNipeQ43HAjqAZjwSe3SEf+q/84FGPHoso3kyntbxa4c4i77t3m6fGf8cw== + dependencies: + is-ssh "^1.3.0" + protocols "^1.4.0" + qs "^6.9.4" + query-string "^6.13.8" + +parse-url@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-6.0.0.tgz#f5dd262a7de9ec00914939220410b66cff09107d" + integrity sha512-cYyojeX7yIIwuJzledIHeLUBVJ6COVLeT4eF+2P6aKVzwvgKQPndCBv3+yQ7pcWjqToYwaligxzSYNNmGoMAvw== + dependencies: + is-ssh "^1.3.0" + normalize-url "^6.1.0" + parse-path "^4.0.0" + protocols "^1.4.0" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +passport-headerapikey@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/passport-headerapikey/-/passport-headerapikey-1.2.2.tgz#b71960523999c9864151b8535c919e3ff5ba75ce" + integrity sha512-4BvVJRrWsNJPrd3UoZfcnnl4zvUWYKEtfYkoDsaOKBsrWHYmzTApCjs7qUbncOLexE9ul0IRiYBFfBG0y9IVQA== + dependencies: + lodash "^4.17.15" + passport-strategy "^1.0.0" + +passport-jwt@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.0.tgz#7f0be7ba942e28b9f5d22c2ebbb8ce96ef7cf065" + integrity sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg== + dependencies: + jsonwebtoken "^8.2.0" + passport-strategy "^1.0.0" + +passport-strategy@1.x.x, passport-strategy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== + +passport@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.6.0.tgz#e869579fab465b5c0b291e841e6cc95c005fac9d" + integrity sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + utils-merge "^1.0.1" + +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +path-to-regexp@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.2.0.tgz#fa7877ecbc495c601907562222453c43cc204a5f" + integrity sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA== + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" + integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== + +pirates@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pluralize@8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier@^2.6.2, prettier@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" + integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== + +pretty-format@^28.0.0, pretty-format@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-28.1.1.tgz#f731530394e0f7fcd95aba6b43c50e02d86b95cb" + integrity sha512-wwJbVTGFHeucr5Jw2bQ9P+VYHyLdAqedFLEkdQUVaBF/eiidDwH5OpilINq4mEfhbCjLnirt6HTTDhv1HaTIQw== + dependencies: + "@jest/schemas" "^28.0.2" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +proc-log@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-2.0.1.tgz#8f3f69a1f608de27878f91f5c688b225391cb685" + integrity sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +promise-all-reject-late@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" + integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw== + +promise-call-limit@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-call-limit/-/promise-call-limit-1.0.1.tgz#4bdee03aeb85674385ca934da7114e9bcd3c6e24" + integrity sha512-3+hgaa19jzCGLuSCbieeRsu5C2joKfYn8pY6JAuXFRVfF4IO+L7UPpFWNTeWT9pM7uhskvbPPd/oEOktCn317Q== + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +promzard@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" + integrity sha512-JZeYqd7UAcHCwI+sTOeUDYkvEU+1bQ7iE0UT1MgB/tERkAPkesW46MrpIySzODi+owTjZtiF8Ay5j9m60KmMBw== + dependencies: + read "1" + +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== + +protocols@^1.1.0, protocols@^1.4.0: + version "1.4.8" + resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8" + integrity sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +q@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== + +qs@6.10.3: + version "6.10.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" + integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== + dependencies: + side-channel "^1.0.4" + +qs@6.9.3: + version "6.9.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e" + integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw== + +qs@^6.10.3, qs@^6.9.4: + version "6.10.5" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.5.tgz#974715920a80ff6a262264acd2c7e6c2a53282b4" + integrity sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ== + dependencies: + side-channel "^1.0.4" + +query-string@^6.13.8: + version "6.14.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" + integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw== + dependencies: + decode-uri-component "^0.2.0" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" + integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +read-cmd-shim@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz#4a50a71d6f0965364938e9038476f7eede3928d9" + integrity sha512-HJpV9bQpkl6KwjxlJcBoqu9Ba0PQg8TqSNIOrulGt54a0uup0HtevreFHzYzkm0lpnleRdNBzXznKrgxglEHQw== + +read-cmd-shim@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-3.0.0.tgz#62b8c638225c61e6cc607f8f4b779f3b8238f155" + integrity sha512-KQDVjGqhZk92PPNRj9ZEXEuqg8bUobSKRw+q0YQ3TKI5xkce7bUJobL4Z/OtiEbAAv70yEpYIXp4iQ9L8oPVog== + +read-package-json-fast@^2.0.2, read-package-json-fast@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz#323ca529630da82cb34b36cc0b996693c98c2b83" + integrity sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ== + dependencies: + json-parse-even-better-errors "^2.3.0" + npm-normalize-package-bin "^1.0.1" + +read-package-json@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-3.0.1.tgz#c7108f0b9390257b08c21e3004d2404c806744b9" + integrity sha512-aLcPqxovhJTVJcsnROuuzQvv6oziQx4zd3JvG0vGCL5MjTONUc4uJ90zCBC6R7W7oUKBNoR/F8pkyfVwlbxqng== + dependencies: + glob "^7.1.1" + json-parse-even-better-errors "^2.3.0" + normalize-package-data "^3.0.0" + npm-normalize-package-bin "^1.0.0" + +read-package-json@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-4.1.2.tgz#b444d047de7c75d4a160cb056d00c0693c1df703" + integrity sha512-Dqer4pqzamDE2O4M55xp1qZMuLPqi4ldk2ya648FOMHRjwMzFhuxVrG04wd0c38IsvkVdr3vgHI6z+QTPdAjrQ== + dependencies: + glob "^7.1.1" + json-parse-even-better-errors "^2.3.0" + normalize-package-data "^3.0.0" + npm-normalize-package-bin "^1.0.0" + +read-package-json@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-5.0.1.tgz#1ed685d95ce258954596b13e2e0e76c7d0ab4c26" + integrity sha512-MALHuNgYWdGW3gKzuNMuYtcSSZbGQm94fAp16xt8VsYTLBjUSc55bLMKe6gzpWue0Tfi6CBgwCSdDAqutGDhMg== + dependencies: + glob "^8.0.1" + json-parse-even-better-errors "^2.3.1" + normalize-package-data "^4.0.0" + npm-normalize-package-bin "^1.0.1" + +read-pkg-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" + integrity sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw== + dependencies: + find-up "^2.0.0" + read-pkg "^3.0.0" + +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + integrity sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA== + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +read@1, read@~1.0.1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" + integrity sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ== + dependencies: + mute-stream "~0.0.4" + +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.2.2, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readdir-glob@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" + integrity sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA== + dependencies: + minimatch "^3.0.4" + +readdir-scoped-modules@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" + integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== + dependencies: + debuglog "^1.0.1" + dezalgo "^1.0.0" + graceful-fs "^4.1.2" + once "^1.3.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +reflect-metadata@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" + integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + +regexp.prototype.flags@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" + integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + functions-have-names "^1.2.2" + +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-dir@^1.0.0, resolve-dir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" + integrity sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg== + dependencies: + expand-tilde "^2.0.0" + global-modules "^1.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-global@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-global/-/resolve-global-1.0.0.tgz#a2a79df4af2ca3f49bf77ef9ddacd322dad19255" + integrity sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw== + dependencies: + global-dirs "^0.1.1" + +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.20.0, resolve@^1.22.0: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +response-time@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/response-time/-/response-time-2.3.2.tgz#ffa71bab952d62f7c1d49b7434355fbc68dffc5a" + integrity sha512-MUIDaDQf+CVqflfTdQ5yam+aYCkXj1PY8fjlPDQ6ppxJlmgZb864pHtA750mayywNg8tx4rS7qH9JXd/OF+3gw== + dependencies: + depd "~1.1.0" + on-headers "~1.0.1" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rotating-file-stream@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/rotating-file-stream/-/rotating-file-stream-3.0.4.tgz#158e21aa229020b1443607e771c634f00bd7bbf1" + integrity sha512-Zd+IA5soqBUKtCa7KfMRnF7GKEaRav/dJFU7UHc+r1FMd8VvMLUFE0q1HX5s6XJO1cZ6jAqtTa/ZFnjc7IqlJA== + +run-async@^2.2.0, run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@6.6.7, rxjs@^6.4.0, rxjs@^6.6.0: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +rxjs@^7.2.0, rxjs@^7.5.5: + version "7.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f" + integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw== + dependencies: + tslib "^2.1.0" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-stable-stringify@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz#ab67cbe1fe7d40603ca641c5e765cb942d04fc73" + integrity sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +saslprep@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" + integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== + dependencies: + sparse-bitfield "^3.0.3" + +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + +schema-utils@^3.1.0, schema-utils@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +"semver@2 || 3 || 4 || 5", semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@7.3.4: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + +semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +semver@^6.0.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-javascript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +setimmediate@^1.0.5, setimmediate@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shelljs@0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +sift@16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/sift/-/sift-16.0.0.tgz#447991577db61f1a8fab727a8a98a6db57a23eb8" + integrity sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ== + +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz#032fb583048a29ebffec2e6a73fca0761f48177e" + integrity sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ== + dependencies: + agent-base "^6.0.2" + debug "4" + socks "^2.3.3" + +socks-proxy-agent@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" + integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks-proxy-agent@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" + integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.3.3, socks@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.2.tgz#ec042d7960073d40d94268ff3bb727dc685f111a" + integrity sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA== + dependencies: + ip "^1.1.5" + smart-buffer "^4.2.0" + +sort-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + integrity sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg== + dependencies: + is-plain-obj "^1.0.0" + +sort-keys@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-4.2.0.tgz#6b7638cee42c506fff8c1cecde7376d21315be18" + integrity sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg== + dependencies: + is-plain-obj "^2.0.0" + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@0.5.21, source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sourcemap-codec@^1.4.4: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ== + dependencies: + memory-pager "^1.0.2" + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.11" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" + integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== + +split-on-first@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" + integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== + +split2@^3.0.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" + integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== + dependencies: + readable-stream "^3.0.0" + +split@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" + integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== + dependencies: + through "2" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + +ssri@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" + integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== + dependencies: + minipass "^3.1.1" + +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== + +stack-utils@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + dependencies: + escape-string-regexp "^2.0.0" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + +strict-uri-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" + integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== + +string-format@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" + integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string.prototype.trimend@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" + integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + +string.prototype.trimstart@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" + integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow== + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@4.0.0, strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" + integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strong-log-transformer@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" + integrity sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA== + dependencies: + duplexer "^0.1.1" + minimist "^1.2.0" + through "^2.3.4" + +superagent@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-7.1.6.tgz#64f303ed4e4aba1e9da319f134107a54cacdc9c6" + integrity sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g== + dependencies: + component-emitter "^1.3.0" + cookiejar "^2.1.3" + debug "^4.3.4" + fast-safe-stringify "^2.1.1" + form-data "^4.0.0" + formidable "^2.0.1" + methods "^1.1.2" + mime "2.6.0" + qs "^6.10.3" + readable-stream "^3.6.0" + semver "^7.3.7" + +supertest@^6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-6.2.3.tgz#291b220126e5faa654d12abe1ada3658757c8c67" + integrity sha512-3GSdMYTMItzsSYjnIcljxMVZKPW1J9kYHZY+7yLfD0wpPwww97GeImZC1oOk0S5+wYl2niJwuFusBJqwLqYM3g== + dependencies: + methods "^1.1.2" + superagent "^7.1.3" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +symbol-observable@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" + integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== + +tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +tar-stream@^2.2.0, tar-stream@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar@^6.0.2, tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +temp-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" + integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +terser-webpack-plugin@^5.1.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz#8033db876dd5875487213e87c627bca323e5ed90" + integrity sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ== + dependencies: + "@jridgewell/trace-mapping" "^0.3.7" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.0" + terser "^5.7.2" + +terser@^5.7.2: + version "5.14.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.1.tgz#7c95eec36436cb11cf1902cc79ac564741d19eca" + integrity sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ== + dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" + commander "^2.20.0" + source-map-support "~0.5.20" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-extensions@^1.0.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" + integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== + +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== + +through2@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through2@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" + integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== + dependencies: + readable-stream "3" + +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +timers-ext@^0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" + integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== + dependencies: + es5-ext "~0.10.46" + next-tick "1" + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmp@^0.2.0, tmp@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tr46@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== + dependencies: + punycode "^2.1.1" + +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ== + +tree-kill@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +treeverse@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/treeverse/-/treeverse-2.0.0.tgz#036dcef04bc3fd79a9b79a68d4da03e882d8a9ca" + integrity sha512-N5gJCkLu1aXccpOTtqV6ddSEi6ZmGkh3hjmbu1IjcavJK4qyOVQmi0myQKM7z5jVGmD68SJoliaVrMmVObhj6A== + +trim-newlines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== + +triple-beam@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" + integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== + +"true-myth@^4.1.0": + version "4.1.1" + resolved "https://registry.yarnpkg.com/true-myth/-/true-myth-4.1.1.tgz#ff4ac9d5130276e34aa338757e2416ec19248ba2" + integrity sha512-rqy30BSpxPznbbTcAcci90oZ1YR4DqvKcNXNerG5gQBU2v4jk0cygheiul5J6ExIMrgDVuanv/MkGfqZbKrNNg== + +ts-jest@^28.0.4, ts-jest@^28.0.5: + version "28.0.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-28.0.5.tgz#31776f768fba6dfc8c061d488840ed0c8eeac8b9" + integrity sha512-Sx9FyP9pCY7pUzQpy4FgRZf2bhHY3za576HMKJFs+OnQ9jS96Du5vNsDKkyedQkik+sEabbKAnCliv9BEsHZgQ== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^28.0.0" + json5 "^2.2.1" + lodash.memoize "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "^21.0.1" + +ts-loader@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.3.0.tgz#980f4dbfb60e517179e15e10ed98e454b132159f" + integrity sha512-2kLLAdAD+FCKijvGKi9sS0OzoqxLCF3CxHpok7rVgCZ5UldRzH0TkbwG9XECKjBzHsAewntC5oDaI/FwKzEUog== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.0.0" + micromatch "^4.0.0" + semver "^7.3.4" + +ts-morph@^13.0.1: + version "13.0.3" + resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-13.0.3.tgz#c0c51d1273ae2edb46d76f65161eb9d763444c1d" + integrity sha512-pSOfUMx8Ld/WUreoSzvMFQG5i9uEiWIsBYjpU9+TTASOeUa89j5HykomeqVULm1oqWtBdleI3KEFRLrlA3zGIw== + dependencies: + "@ts-morph/common" "~0.12.3" + code-block-writer "^11.0.0" + +ts-node@^10.8.0, ts-node@^10.8.1: + version "10.8.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.8.1.tgz#ea2bd3459011b52699d7e88daa55a45a1af4f066" + integrity sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +ts-prune@^0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/ts-prune/-/ts-prune-0.10.3.tgz#b6c71a525543b38dcf947a7d3adfb7f9e8b91f38" + integrity sha512-iS47YTbdIcvN8Nh/1BFyziyUqmjXz7GVzWu02RaZXqb+e/3Qe1B7IQ4860krOeCGUeJmterAlaM2FRH0Ue0hjw== + dependencies: + commander "^6.2.1" + cosmiconfig "^7.0.1" + json5 "^2.1.3" + lodash "^4.17.21" + "true-myth" "^4.1.0" + ts-morph "^13.0.1" + +tsconfig-paths-webpack-plugin@3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.2.tgz#01aafff59130c04a8c4ebc96a3045c43c376449a" + integrity sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.7.0" + tsconfig-paths "^3.9.0" + +tsconfig-paths@3.14.1, tsconfig-paths@^3.14.1, tsconfig-paths@^3.9.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" + integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tsconfig-paths@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.0.0.tgz#1082f5d99fd127b72397eef4809e4dd06d229b64" + integrity sha512-SLBg2GBKlR6bVtMgJJlud/o3waplKtL7skmLkExomIiaAtLGtVsoXIqP3SYdjbcH9lq/KVv7pMZeCBpLYOit6Q== + dependencies: + json5 "^2.2.1" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +tslib@2.4.0, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" + integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8" + integrity sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-is@^1.6.4, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.6.0.tgz#3ca6099af5981d36ca86b78442973694278a219f" + integrity sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + +typescript@4.6.4: + version "4.6.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9" + integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== + +typescript@^4.6.4, typescript@^4.7.3, typescript@^4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== + +ua-parser-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.2.tgz#e2976c34dbfb30b15d2c300b2a53eac87c57a775" + integrity sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg== + +uglify-js@^3.1.4: + version "3.16.1" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.16.1.tgz#0e7ec928b3d0b1e1d952bce634c384fd56377317" + integrity sha512-X5BGTIDH8U6IQ1TIRP62YC36k+ULAa1d59BxlWvPUJ1NkW5L3FwcGfEzuVvGmhJFBu0YJ5Ge25tmRISqCmLiRQ== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +unzipper@^0.10.11: + version "0.10.11" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.11.tgz#0b4991446472cbdb92ee7403909f26c2419c782e" + integrity sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "^1.0.12" + graceful-fs "^4.2.2" + listenercount "~1.0.1" + readable-stream "~2.3.6" + setimmediate "~1.0.4" + +upath@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" + integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== + +update-browserslist-db@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.3.tgz#6c47cb996f34afb363e924748e2f6e4d983c6fc1" + integrity sha512-ufSazemeh9Gty0qiWtoRpJ9F5Q5W3xdIPm1UZQqYQv/q0Nyb9EMHUB2lu+O9x1re9WsorpMAUu4Y6Lxcs5n+XQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utils-merge@1.0.1, utils-merge@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@8.3.2, uuid@^8.3.0, uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-compile-cache@2.3.0, v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +v8-to-istanbul@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz#b6f994b0b5d4ef255e17a0d17dc444a9f5132fa4" + integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + +validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +validate-npm-package-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" + integrity sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw== + dependencies: + builtins "^1.0.3" + +validate-npm-package-name@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz#fe8f1c50ac20afdb86f177da85b3600f0ac0d747" + integrity sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q== + dependencies: + builtins "^5.0.0" + +validator@^13.7.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" + integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== + +vary@^1, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +vscode-languageserver-textdocument@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.5.tgz#838769940ece626176ec5d5a2aa2d0aa69f5095c" + integrity sha512-1ah7zyQjKBudnMiHbZmxz5bYNM9KKZYz+5VQLj+yr8l+9w3g+WAhCkUkWbhMEdC5u0ub4Ndiye/fDyS8ghIKQg== + +vscode-uri@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" + integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA== + +walk-up-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" + integrity sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg== + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +watchpack@^2.3.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wcwidth@^1.0.0, wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +webpack-node-externals@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz#1a3407c158d547a9feb4229a9e3385b7b60c9917" + integrity sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ== + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@5.72.1: + version "5.72.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.72.1.tgz#3500fc834b4e9ba573b9f430b2c0a61e1bb57d13" + integrity sha512-dXG5zXCLspQR4krZVR6QgajnZOjW2K/djHvdcRaDQvsjV9z9vaW6+ja5dZOYbqBBjF6kGXka/2ZyxNdc+8Jung== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + acorn "^8.4.1" + acorn-import-assertions "^1.7.6" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.9.3" + es-module-lexer "^0.9.0" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.1.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.3" + watchpack "^2.3.1" + webpack-sources "^3.2.3" + +webpack@^5.73.0: + version "5.73.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.73.0.tgz#bbd17738f8a53ee5760ea2f59dce7f3431d35d38" + integrity sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + acorn "^8.4.1" + acorn-import-assertions "^1.7.6" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.9.3" + es-module-lexer "^0.9.0" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.1.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.3" + watchpack "^2.3.1" + webpack-sources "^3.2.3" + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +whatwg-url@^8.4.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== + dependencies: + lodash "^4.7.0" + tr46 "^2.1.0" + webidl-conversions "^6.1.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which@^1.2.14: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.2, wide-align@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +windows-release@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-4.0.0.tgz#4725ec70217d1bf6e02c7772413b29cdde9ec377" + integrity sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg== + dependencies: + execa "^4.0.2" + +winston-daily-rotate-file@^4.7.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/winston-daily-rotate-file/-/winston-daily-rotate-file-4.7.1.tgz#f60a643af87f8867f23170d8cd87dbe3603a625f" + integrity sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA== + dependencies: + file-stream-rotator "^0.6.1" + object-hash "^2.0.1" + triple-beam "^1.3.0" + winston-transport "^4.4.0" + +winston-transport@^4.4.0, winston-transport@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa" + integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q== + dependencies: + logform "^2.3.2" + readable-stream "^3.6.0" + triple-beam "^1.3.0" + +winston@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.7.2.tgz#95b4eeddbec902b3db1424932ac634f887c400b1" + integrity sha512-QziIqtojHBoyzUOdQvQiar1DH0Xp9nF1A1y7NVy2DGEsz82SBDtOalS0ulTRGVT14xPX3WRWkCsdcJKqNflKng== + dependencies: + "@dabh/diagnostics" "^2.0.2" + async "^3.2.3" + is-stream "^2.0.0" + logform "^2.4.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.5.0" + +word-wrap@^1.0.3, word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^2.4.2: + version "2.4.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" + integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +write-file-atomic@^4.0.0, write-file-atomic@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.1.tgz#9faa33a964c1c85ff6f849b80b42a88c2c537c8f" + integrity sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +write-json-file@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-3.2.0.tgz#65bbdc9ecd8a1458e15952770ccbadfcff5fe62a" + integrity sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ== + dependencies: + detect-indent "^5.0.0" + graceful-fs "^4.1.15" + make-dir "^2.1.0" + pify "^4.0.1" + sort-keys "^2.0.0" + write-file-atomic "^2.4.2" + +write-json-file@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-4.3.0.tgz#908493d6fd23225344af324016e4ca8f702dd12d" + integrity sha512-PxiShnxf0IlnQuMYOPPhPkhExoCQuTUNPOa/2JWCYTmBquU9njyyDuwRKN26IZBlp4yn1nt+Agh2HOOBl+55HQ== + dependencies: + detect-indent "^6.0.0" + graceful-fs "^4.1.15" + is-plain-obj "^2.0.0" + make-dir "^3.0.0" + sort-keys "^4.0.0" + write-file-atomic "^3.0.0" + +write-pkg@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-4.0.0.tgz#675cc04ef6c11faacbbc7771b24c0abbf2a20039" + integrity sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA== + dependencies: + sort-keys "^2.0.0" + type-fest "^0.4.1" + write-json-file "^3.2.0" + +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +xtend@^4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + +yargs-parser@21.0.1, yargs-parser@^21.0.0, yargs-parser@^21.0.1: + version "21.0.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" + integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== + +yargs-parser@^20.2.2, yargs-parser@^20.2.3: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.3.1, yargs@^17.4.0, yargs@^17.5.1: + version "17.5.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e" + integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zip-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79" + integrity sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A== + dependencies: + archiver-utils "^2.1.0" + compress-commons "^4.1.0" + readable-stream "^3.6.0"