[
  {
    "path": ".dockerignore",
    "content": "# Versioning and metadata\n.vscode\n.git\n.gitignore\n.dockerignore\n\n# Build dependencies\ndist\nnode_modules\ncoverage\n\n# Environment (contains sensitive data)\n.env\n\n# Files not required for production\nDockerfile\nREADME.md\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  parser: '@typescript-eslint/parser',\n  parserOptions: {\n    project: 'tsconfig.json',\n    tsconfigRootDir: __dirname,\n    sourceType: 'module',\n  },\n  plugins: ['@typescript-eslint/eslint-plugin'],\n  extends: [\n    'plugin:@typescript-eslint/recommended',\n    'plugin:prettier/recommended',\n  ],\n  root: true,\n  env: {\n    node: true,\n    jest: true,\n  },\n  ignorePatterns: ['.eslintrc.js'],\n  rules: {\n    '@typescript-eslint/interface-name-prefix': 'off',\n    '@typescript-eslint/explicit-function-return-type': 'off',\n    '@typescript-eslint/explicit-module-boundary-types': 'off',\n    '@typescript-eslint/no-explicit-any': 'off',\n    '@typescript-eslint/no-unused-vars': 'off',\n  },\n};\n"
  },
  {
    "path": ".github/actions/setvars/action.yml",
    "content": "name: 'Set environment variables'\ndescription: 'Configures environment variables for a workflow'\ninputs:\n  varFilePath:\n    description: 'File path to variable file or directory. Defaults to ./.github/variables/* if none specified and runs against each file in that directory.'\n    required: false\n    default: ./.github/variables/*\nruns:\n  using: \"composite\"\n  steps:\n    - run: |\n        sed \"\" ${{ inputs.varFilePath }} >> $GITHUB_ENV\n      shell: bash"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: npm\n    directory: '/'\n    schedule:\n      interval: 'daily'\n    open-pull-requests-limit: 10\n    labels:\n      - 'dependencies'\n  - package-ecosystem: 'github-actions'\n    directory: '/'\n    schedule:\n      interval: 'daily'\n"
  },
  {
    "path": ".github/variables/myvars.env",
    "content": "NODE_ENV=test\nPORT=3000\n\nDB_HOST=localhost\nDB_PORT=3306\nDB_USER=admin\nDB_PASSWORD=test123!\nDB_NAME=nestjs-auth\n\nREDIS_HOST=localhost\nREDIS_PORT=6379\nREDIS_USERNAME=\nREDIS_PASSWORD=\nREDIS_DATABASE=1\nREDIS_KEY_PREFIX=nestjs-auth\n\nJWT_SECRET=your-secret-key\nJWT_ACCESS_TOKEN_TTL=86400\n\nSWAGGER_SITE_TITLE=The NestJs Authentication API\nSWAGGER_DOC_TITLE=NestJs Authentication\nSWAGGER_DOC_DESCRIPTION=The NestJs Authentication API\nSWAGGER_DOC_VERSION=1.0"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI Testing\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    types: [opened, synchronize, reopened]\n\njobs:\n  unit-tests:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [18.x, 20.x]\n        test: [nestjs-authentication-and-authorization]\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: Use NodeJS ${{ matrix.node-version }}\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n\n      - name: Setup npm\n        run: npm install -g npm\n\n      - name: Setup Nodejs with npm caching\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n          cache: npm\n\n      - name: Install dependencies\n        run: npm i\n\n      - name: Run unit test\n        run: npm run test:unit\n\n  e2e-test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [18.x, 20.x]\n    needs: [unit-tests]\n    steps:\n      - uses: actions/checkout@v6\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n\n      - name: Setup npm\n        run: npm install -g npm\n\n      - name: Setup Nodejs with npm caching\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n          cache: npm\n\n      - name: Set Environment Variables\n        uses: ./.github/actions/setvars\n        with:\n          varFilePath: ./.github/variables/myvars.env\n\n      - name: Start Docker-Compose\n        run: docker-compose -f docker-compose-test.yml up -d\n\n      - name: Install dependencies\n        run: npm i\n\n      - name: Run tests\n        run: npm run test:e2e\n\n      - name: Stop Docker-Compose\n        run: docker-compose -f docker-compose-test.yml down\n"
  },
  {
    "path": ".github/workflows/dependabot-auto-merge.yml",
    "content": "name: Dependabot auto-merge PRs if CI Testing succeeds\n\non:\n  pull_request_target:\n  workflow_run:\n    workflows: ['CI Testing']\n    types:\n      - completed\n\npermissions:\n  pull-requests: write\n  contents: write\n\njobs:\n  dependabot:\n    runs-on: ubuntu-latest\n    if: ${{ github.actor == 'dependabot[bot]' }} && ${{ github.event.workflow_run.conclusion == 'success' }}\n    steps:\n      - name: Dependabot metadata\n        id: metadata\n        uses: dependabot/fetch-metadata@v3.1.0\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Enable auto-merge for Dependabot PRs\n        # if: contains(steps.metadata.outputs.dependency-names, 'my-dependency') && steps.metadata.outputs.update-type == 'version-update:semver-patch'\n        run: gh pr merge --auto --rebase --delete-branch \"${{ github.event.pull_request.html_url }}\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# compiled output\n/dist\n/node_modules\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\npnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# OS\n.DS_Store\n\n# Tests\n/coverage\n/.nyc_output\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# Environment\n.env\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": ".husky/pre-push",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpm run test:unit && npm run test:e2e\n"
  },
  {
    "path": ".lintstagedrc.json",
    "content": "{\n  \"**/*.{js,jsx,ts,tsx}\": [\n    \"eslint --fix\",\n    \"prettier --config ./.prettierrc --write\"\n  ],\n  \"**/*.{css,scss,md,html,json}\": [\"prettier --config ./.prettierrc --write\"]\n}\n"
  },
  {
    "path": ".prettierignore",
    "content": "dist/\nnode_modules/\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\"\n}"
  },
  {
    "path": ".swcrc",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/swcrc\",\n  \"sourceMaps\": true,\n  \"jsc\": {\n    \"parser\": {\n      \"syntax\": \"typescript\",\n      \"decorators\": true,\n      \"dynamicImport\": true\n    },\n    \"transform\": {\n      \"legacyDecorator\": true,\n      \"decoratorMetadata\": true\n    },\n    \"target\": \"es2017\",\n    \"keepClassNames\": true,\n    \"baseUrl\": \"./\"\n  },\n  \"module\": {\n    \"type\": \"commonjs\",\n    \"strictMode\": true\n  }\n}\n"
  },
  {
    "path": "Dockerfile",
    "content": "###################\n# BUILD FOR LOCAL DEVELOPMENT\n###################\n\nFROM node:18-alpine As development\n\n# Create app directory\nWORKDIR /usr/src/app\n\n# Copy application dependency manifests to the container image.\n# A wildcard is used to ensure copying both package.json AND package-lock.json (when available).\n# Copying this first prevents re-running npm install on every code change.\nCOPY --chown=node:node package*.json ./\n\n# Install app dependencies using the `npm ci` command instead of `npm install`\nRUN npm ci\n\n# Bundle app source\nCOPY --chown=node:node . .\n\n# Use the node user from the image (instead of the root user)\nUSER node\n\n###################\n# BUILD FOR PRODUCTION\n###################\n\nFROM node:18-alpine As build\n\nWORKDIR /usr/src/app\n\nCOPY --chown=node:node package*.json ./\n\n# In order to run `npm run build` we need access to the Nest CLI.\n# The Nest CLI is a dev dependency,\n# In the previous development stage we ran `npm ci` which installed all dependencies.\n# So we can copy over the node_modules directory from the development image into this build image.\nCOPY --chown=node:node --from=development /usr/src/app/node_modules ./node_modules\n\nCOPY --chown=node:node . .\n\n# Run the build command which creates the production bundle\nRUN npm run build\n\n# Set NODE_ENV environment variable\nENV NODE_ENV production\n\n# Running `npm ci` removes the existing node_modules directory.\n# Passing in --only=production ensures that only the production dependencies are installed.\n# This ensures that the node_modules directory is as optimized as possible.\nRUN npm ci --only=production && npm cache clean --force\n\nUSER node\n\n###################\n# PRODUCTION\n###################\n\nFROM node:18-alpine As production\n\n# Copy the bundled code from the build stage to the production image\nCOPY --chown=node:node --from=build /usr/src/app/node_modules ./node_modules\nCOPY --chown=node:node --from=build /usr/src/app/dist ./dist\n\n# Start the server using the production build\nCMD [ \"node\", \"dist/main.js\" ]"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Anil Ahir\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# NestJS Authentication\n\n![Workflow Test](https://github.com/anilahir/nestjs-authentication-and-authorization/actions/workflows/ci.yml/badge.svg)\n![Prettier](https://img.shields.io/badge/Code%20style-prettier-informational?logo=prettier&logoColor=white)\n[![GPL v3 License](https://img.shields.io/badge/License-GPLv3-green.svg)](./LICENSE)\n[![HitCount](https://hits.dwyl.com/anilahir/nestjs-authentication-and-authorization.svg)](https://hits.dwyl.com/anilahir/nestjs-authentication-and-authorization)\n\n## Description\n\nNestJS Authentication without Passport using Bcrypt, JWT and Redis\n\n## Features\n\n1. Register\n2. Login\n3. Show profile\n4. Logout\n\n## Technologies stack:\n\n- JWT\n- Bcrypt\n- TypeORM + MySQL\n- Redis\n- Docker\n\n## Setup\n\n### 1. Install the required dependencies\n\n```bash\n$ npm install\n```\n\n### 2. Rename the .env.example filename to .env and set your local variables\n\n```bash\n$ mv .env.example .env\n```\n\n### 3. Start the application\n\n```bash\n# development\n$ npm run start\n\n# watch mode\n$ npm run start:dev\n\n# production mode\n$ npm run start:prod\n```\n\n## Docker for development\n\n```bash\n# start the application\n$ npm run docker:up\n\n# stop the application\n$ npm run docker:down\n```\n\n## Swagger documentation\n\n- [localhost:3000/docs](http://localhost:3000/docs)\n\n## References\n\n- [NestJS Authentication without Passport](https://trilon.io/blog/nestjs-authentication-without-passport)\n- [NestJS, Redis and Postgres local development with Docker Compose](https://www.tomray.dev/nestjs-docker-compose-postgres)\n\n## Author\n\n👤 **Anil Ahir**\n\n- Twitter: [@anilahir220](https://twitter.com/anilahir220)\n- Github: [@anilahir](https://github.com/anilahir)\n- LinkedIn: [@anilahir](https://www.linkedin.com/in/anilahir)\n\n## Show your support\n\nGive a ⭐️ if this project helped you!\n\n## Related projects\n\nExplore more NestJS example projects:\n\n[![GraphQL example](https://github-readme-stats.vercel.app/api/pin/?username=anilahir&repo=nestjs-graphql-demo)](https://github.com/anilahir/nestjs-graphql-demo)\n\n## License\n\nRelease under the terms of [MIT](./LICENSE)\n"
  },
  {
    "path": "docker-compose-test.yml",
    "content": "services:\n  mysql:\n    image: mysql:8.0\n    ports:\n      - 3306:3306\n    environment:\n      MYSQL_RANDOM_ROOT_PASSWORD: 'true'\n      MYSQL_USER: admin\n      MYSQL_PASSWORD: test123!\n      MYSQL_DATABASE: nestjs-auth\n      TZ: 'utc'\n    command: --default-authentication-plugin=mysql_native_password\n\n  redis:\n    image: redis:alpine\n    ports:\n      - 6379:6379\n\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  nestjs-auth-api:\n    container_name: nestjs-auth-api\n    image: nestjs-auth-api\n    restart: unless-stopped\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: development # Only will build development stage from our dockerfile\n    volumes:\n      - ./:/usr/src/app\n    ports:\n      - ${PORT}:${PORT}\n    networks:\n      - nestjs-auth-intranet\n    env_file: \n      - .env # Available inside container not in compose file\n    environment:\n      - DB_HOST=nestjs-auth-mysql\n      - REDIS_HOST=nestjs-auth-redis\n    depends_on:\n      nestjs-auth-mysql:\n        condition: service_healthy\n      nestjs-auth-redis:\n        condition: service_healthy\n    command: npm run start:dev # Run in development mode\n\n  nestjs-auth-mysql:\n    container_name: nestjs-auth-mysql\n    image: mysql:8.0\n    restart: unless-stopped\n    volumes:\n      - mysql:/var/lib/mysql\n    ports:\n      - 3307:${DB_PORT}\n    networks:\n      - nestjs-auth-intranet\n    environment:\n      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}\n      MYSQL_DATABASE: ${DB_NAME}\n      MYSQL_USER: ${DB_USER}\n      MYSQL_PASSWORD: ${DB_PASSWORD}\n      TZ: 'utc'\n    command: --default-authentication-plugin=mysql_native_password\n    healthcheck:\n      test: ['CMD', 'mysqladmin', '-u${DB_USER}', '-p${DB_PASSWORD}', 'ping']\n      interval: 5s\n      retries: 3\n      timeout: 3s\n\n  nestjs-auth-redis:\n    container_name: nestjs-auth-redis\n    image: redis:alpine\n    restart: unless-stopped\n    volumes:\n      - redis:/data\n    ports:\n      - 6380:${REDIS_PORT}\n    networks:\n      - nestjs-auth-intranet\n    healthcheck:\n      test: ['CMD', 'redis-cli', 'ping']\n      interval: 5s\n      retries: 3\n      timeout: 3s\n\nvolumes:\n  mysql:\n    name: nestjs-auth-mysql\n  redis:\n    name: nestjs-auth-redis\n\nnetworks:\n  nestjs-auth-intranet:\n    name: nestjs-auth-intranet\n    driver: bridge\n"
  },
  {
    "path": "nest-cli.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/nest-cli\",\n  \"collection\": \"@nestjs/schematics\",\n  \"sourceRoot\": \"src\",\n  \"compilerOptions\": {\n    \"plugins\": [\n      {\n        \"name\": \"@nestjs/swagger\",\n        \"options\": {\n          \"dtoFileNameSuffix\": [\".entity.ts\", \".dto.ts\"],\n          \"controllerFileNameSuffix\": [\".controller.ts\"],\n          \"classValidatorShim\": true,\n          \"dtoKeyOfComment\": \"description\",\n          \"controllerKeyOfComment\": \"description\",\n          \"introspectComments\": true\n        }\n      }\n    ],\n    \"builder\": \"swc\",\n    \"typeCheck\": true\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"nestjs-authentication-and-authorization\",\n  \"version\": \"0.0.1\",\n  \"description\": \"NestJS authentication and authorization using Bcrypt, JWT & Redis\",\n  \"author\": \"Anil Ahir\",\n  \"private\": false,\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"prebuild\": \"rimraf dist\",\n    \"build\": \"nest build\",\n    \"format\": \"prettier --write \\\"src/**/*.ts\\\" \\\"test/**/*.ts\\\"\",\n    \"start\": \"nest start\",\n    \"start:dev\": \"nest start --watch\",\n    \"start:debug\": \"nest start --debug --watch\",\n    \"start:prod\": \"node dist/main\",\n    \"lint\": \"eslint \\\"{src,apps,libs,test}/**/*.ts\\\" --fix\",\n    \"test:watch\": \"jest --config ./test/unit/jest-unit.json --watch\",\n    \"test:cov\": \"jest --coverage\",\n    \"test:debug\": \"node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand\",\n    \"test:e2e\": \"NODE_ENV=test jest --config ./test/e2e/jest-e2e.json --runInBand --detectOpenHandles\",\n    \"test:unit\": \"NODE_ENV=test jest --config ./test/unit/jest-unit.json --runInBand\",\n    \"prepare\": \"husky install\",\n    \"docker:up\": \"docker-compose up -d -V --build\",\n    \"docker:down\": \"docker-compose down\"\n  },\n  \"dependencies\": {\n    \"@nestjs/common\": \"^11.1.19\",\n    \"@nestjs/config\": \"^4.0.4\",\n    \"@nestjs/core\": \"^11.1.19\",\n    \"@nestjs/jwt\": \"^11.0.2\",\n    \"@nestjs/mapped-types\": \"*\",\n    \"@nestjs/platform-express\": \"^11.1.19\",\n    \"@nestjs/swagger\": \"^11.4.1\",\n    \"@nestjs/typeorm\": \"^11.0.1\",\n    \"bcrypt\": \"^6.0.0\",\n    \"class-transformer\": \"^0.5.1\",\n    \"class-validator\": \"^0.15.1\",\n    \"ioredis\": \"^5.10.1\",\n    \"mysql2\": \"^3.22.2\",\n    \"reflect-metadata\": \"^0.2.2\",\n    \"rimraf\": \"^6.1.3\",\n    \"rxjs\": \"^7.8.2\",\n    \"swagger-ui-express\": \"^5.0.1\",\n    \"typeorm\": \"^0.3.28\"\n  },\n  \"devDependencies\": {\n    \"@golevelup/ts-jest\": \"^3.0.0\",\n    \"@nestjs/cli\": \"^11.0.21\",\n    \"@nestjs/schematics\": \"^11.1.0\",\n    \"@nestjs/testing\": \"^11.1.19\",\n    \"@swc/cli\": \"^0.8.1\",\n    \"@swc/core\": \"^1.15.30\",\n    \"@swc/jest\": \"^0.2.39\",\n    \"@types/bcrypt\": \"^6.0.0\",\n    \"@types/express\": \"^5.0.6\",\n    \"@types/jest\": \"30.0.0\",\n    \"@types/node\": \"^25.6.0\",\n    \"@types/supertest\": \"^7.2.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.59.0\",\n    \"@typescript-eslint/parser\": \"^8.59.0\",\n    \"eslint\": \"^10.2.1\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-plugin-prettier\": \"^5.5.5\",\n    \"husky\": \"^9.1.7\",\n    \"jest\": \"^30.3.0\",\n    \"lint-staged\": \"^16.4.0\",\n    \"prettier\": \"^3.8.3\",\n    \"source-map-support\": \"^0.5.21\",\n    \"supertest\": \"^7.2.2\",\n    \"ts-jest\": \"29.4.9\",\n    \"ts-loader\": \"^9.5.7\",\n    \"ts-node\": \"^10.9.2\",\n    \"tsconfig-paths\": \"4.2.0\",\n    \"typescript\": \"^6.0.3\"\n  },\n  \"engines\": {\n    \"node\": \">= 18\"\n  },\n  \"jest\": {\n    \"moduleFileExtensions\": [\n      \"js\",\n      \"json\",\n      \"ts\"\n    ],\n    \"rootDir\": \"src\",\n    \"testRegex\": \".*\\\\.spec\\\\.ts$\",\n    \"transform\": {\n      \"^.+\\\\.(t|j)s?$\": [\n        \"@swc/jest\"\n      ]\n    },\n    \"collectCoverageFrom\": [\n      \"**/*.(t|j)s\"\n    ],\n    \"coverageDirectory\": \"../coverage\",\n    \"testEnvironment\": \"node\"\n  }\n}\n"
  },
  {
    "path": "src/app.controller.ts",
    "content": "import { Controller, Get } from '@nestjs/common';\nimport { ApiOkResponse } from '@nestjs/swagger';\n\nimport { AppService } from './app.service';\nimport { Public } from './common/decorators/public.decorator';\n\n@Controller()\nexport class AppController {\n  constructor(private readonly appService: AppService) {}\n\n  @ApiOkResponse({ description: \"Returns 'Hello World'\" })\n  @Public()\n  @Get()\n  getHello(): string {\n    return this.appService.getHello();\n  }\n}\n"
  },
  {
    "path": "src/app.module.ts",
    "content": "import { Module } from '@nestjs/common';\nimport { ConfigModule } from '@nestjs/config';\nimport { APP_GUARD } from '@nestjs/core';\n\nimport { AppController } from './app.controller';\nimport { AppService } from './app.service';\nimport { AuthModule } from './auth/auth.module';\nimport appConfig from './common/config/app.config';\nimport databaseConfig from './common/config/database.config';\nimport jwtConfig from './common/config/jwt.config';\nimport { validate } from './common/validation/env.validation';\nimport { DatabaseModule } from './database/database.module';\nimport { UsersModule } from './users/users.module';\nimport { JwtAuthGuard } from './auth/guards/jwt-auth.guard';\nimport redisConfig from './common/config/redis.config';\nimport { RedisModule } from './redis/redis.module';\nimport swaggerConfig from './common/config/swagger.config';\n\n@Module({\n  imports: [\n    ConfigModule.forRoot({\n      isGlobal: true,\n      load: [appConfig, jwtConfig, databaseConfig, redisConfig, swaggerConfig],\n      validate,\n    }),\n    DatabaseModule,\n    RedisModule,\n    AuthModule,\n    UsersModule,\n  ],\n  controllers: [AppController],\n  providers: [\n    AppService,\n    {\n      provide: APP_GUARD,\n      useClass: JwtAuthGuard,\n    },\n  ],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "src/app.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\n\n@Injectable()\nexport class AppService {\n  getHello(): string {\n    return 'Hello World!';\n  }\n}\n"
  },
  {
    "path": "src/auth/auth.controller.ts",
    "content": "import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';\nimport {\n  ApiBadRequestResponse,\n  ApiBearerAuth,\n  ApiConflictResponse,\n  ApiCreatedResponse,\n  ApiOkResponse,\n  ApiTags,\n  ApiUnauthorizedResponse,\n} from '@nestjs/swagger';\n\nimport { ActiveUser } from '../common/decorators/active-user.decorator';\nimport { Public } from '../common/decorators/public.decorator';\nimport { AuthService } from './auth.service';\nimport { SignInDto } from './dto/sign-in.dto';\nimport { SignUpDto } from './dto/sign-up.dto';\n\n@ApiTags('auth')\n@Controller('auth')\nexport class AuthController {\n  constructor(private readonly authService: AuthService) {}\n\n  @ApiConflictResponse({\n    description: 'User already exists',\n  })\n  @ApiBadRequestResponse({\n    description: 'Return errors for invalid sign up fields',\n  })\n  @ApiCreatedResponse({\n    description: 'User has been successfully signed up',\n  })\n  @Public()\n  @Post('sign-up')\n  signUp(@Body() signUpDto: SignUpDto): Promise<void> {\n    return this.authService.signUp(signUpDto);\n  }\n\n  @ApiBadRequestResponse({\n    description: 'Return errors for invalid sign in fields',\n  })\n  @ApiOkResponse({ description: 'User has been successfully signed in' })\n  @HttpCode(HttpStatus.OK)\n  @Public()\n  @Post('sign-in')\n  signIn(@Body() signInDto: SignInDto): Promise<{ accessToken: string }> {\n    return this.authService.signIn(signInDto);\n  }\n\n  @ApiUnauthorizedResponse({ description: 'Unauthorized' })\n  @ApiOkResponse({ description: 'User has been successfully signed out' })\n  @ApiBearerAuth()\n  @HttpCode(HttpStatus.OK)\n  @Post('sign-out')\n  signOut(@ActiveUser('id') userId: string): Promise<void> {\n    return this.authService.signOut(userId);\n  }\n}\n"
  },
  {
    "path": "src/auth/auth.module.ts",
    "content": "import { Module } from '@nestjs/common';\nimport { JwtModule } from '@nestjs/jwt';\nimport { TypeOrmModule } from '@nestjs/typeorm';\n\nimport { AuthService } from './auth.service';\nimport { AuthController } from './auth.controller';\nimport { BcryptService } from './bcrypt.service';\nimport { User } from '../users/entities/user.entity';\nimport jwtConfig from '../common/config/jwt.config';\n\n@Module({\n  imports: [\n    TypeOrmModule.forFeature([User]),\n    JwtModule.registerAsync(jwtConfig.asProvider()),\n  ],\n  controllers: [AuthController],\n  providers: [AuthService, BcryptService],\n  exports: [JwtModule],\n})\nexport class AuthModule {}\n"
  },
  {
    "path": "src/auth/auth.service.ts",
    "content": "import {\n  BadRequestException,\n  ConflictException,\n  Inject,\n  Injectable,\n} from '@nestjs/common';\nimport { ConfigType } from '@nestjs/config';\nimport { JwtService } from '@nestjs/jwt';\nimport { InjectRepository } from '@nestjs/typeorm';\nimport { randomUUID } from 'crypto';\nimport { Repository } from 'typeorm';\n\nimport jwtConfig from '../common/config/jwt.config';\nimport { MysqlErrorCode } from '../common/enums/error-codes.enum';\nimport { ActiveUserData } from '../common/interfaces/active-user-data.interface';\nimport { RedisService } from '../redis/redis.service';\nimport { User } from '../users/entities/user.entity';\nimport { BcryptService } from './bcrypt.service';\nimport { SignInDto } from './dto/sign-in.dto';\nimport { SignUpDto } from './dto/sign-up.dto';\n\n@Injectable()\nexport class AuthService {\n  constructor(\n    @Inject(jwtConfig.KEY)\n    private readonly jwtConfiguration: ConfigType<typeof jwtConfig>,\n    private readonly bcryptService: BcryptService,\n    private readonly jwtService: JwtService,\n    @InjectRepository(User)\n    private readonly userRepository: Repository<User>,\n    private readonly redisService: RedisService,\n  ) {}\n\n  async signUp(signUpDto: SignUpDto): Promise<void> {\n    const { email, password } = signUpDto;\n\n    try {\n      const user = new User();\n      user.email = email;\n      user.password = await this.bcryptService.hash(password);\n      await this.userRepository.save(user);\n    } catch (error) {\n      if (error.code === MysqlErrorCode.UniqueViolation) {\n        throw new ConflictException(`User [${email}] already exist`);\n      }\n      throw error;\n    }\n  }\n\n  async signIn(signInDto: SignInDto): Promise<{ accessToken: string }> {\n    const { email, password } = signInDto;\n\n    const user = await this.userRepository.findOne({\n      where: {\n        email,\n      },\n    });\n    if (!user) {\n      throw new BadRequestException('Invalid email');\n    }\n\n    const isPasswordMatch = await this.bcryptService.compare(\n      password,\n      user.password,\n    );\n    if (!isPasswordMatch) {\n      throw new BadRequestException('Invalid password');\n    }\n\n    return await this.generateAccessToken(user);\n  }\n\n  async signOut(userId: string): Promise<void> {\n    this.redisService.delete(`user-${userId}`);\n  }\n\n  async generateAccessToken(\n    user: Partial<User>,\n  ): Promise<{ accessToken: string }> {\n    const tokenId = randomUUID();\n\n    await this.redisService.insert(`user-${user.id}`, tokenId);\n\n    const accessToken = await this.jwtService.signAsync(\n      {\n        id: user.id,\n        email: user.email,\n        tokenId,\n      } as ActiveUserData,\n      {\n        secret: this.jwtConfiguration.secret,\n        expiresIn: this.jwtConfiguration.accessTokenTtl,\n      },\n    );\n\n    return { accessToken };\n  }\n}\n"
  },
  {
    "path": "src/auth/bcrypt.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { compare, genSalt, hash } from 'bcrypt';\n\n@Injectable()\nexport class BcryptService {\n  async hash(data: string): Promise<string> {\n    const salt = await genSalt();\n    return hash(data, salt);\n  }\n\n  async compare(data: string, encrypted: string): Promise<boolean> {\n    return compare(data, encrypted);\n  }\n}\n"
  },
  {
    "path": "src/auth/dto/sign-in.dto.ts",
    "content": "import { ApiProperty } from '@nestjs/swagger';\nimport {\n  IsEmail,\n  IsNotEmpty,\n  Matches,\n  MaxLength,\n  MinLength,\n} from 'class-validator';\n\nexport class SignInDto {\n  @ApiProperty({\n    description: 'Email of user',\n    example: 'atest@email.com',\n  })\n  @IsEmail()\n  @MaxLength(255)\n  @IsNotEmpty()\n  readonly email: string;\n\n  @ApiProperty({\n    description: 'Password of user',\n    example: 'Pass#123',\n  })\n  @MinLength(8, {\n    message: 'password too short',\n  })\n  @MaxLength(20, {\n    message: 'password too long',\n  })\n  @Matches(/((?=.*\\d)|(?=.*\\W+))(?![.\\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {\n    message: 'password too weak',\n  })\n  @IsNotEmpty()\n  readonly password: string;\n}\n"
  },
  {
    "path": "src/auth/dto/sign-up.dto.ts",
    "content": "import { ApiProperty } from '@nestjs/swagger';\nimport {\n  IsEmail,\n  IsNotEmpty,\n  Matches,\n  MaxLength,\n  MinLength,\n} from 'class-validator';\n\nimport { Match } from '../../common/decorators/match.decorator';\n\nexport class SignUpDto {\n  @ApiProperty({\n    example: 'atest@email.com',\n    description: 'Email of user',\n  })\n  @IsEmail()\n  @MaxLength(255)\n  @IsNotEmpty()\n  readonly email: string;\n\n  @ApiProperty({\n    description: 'Password of user',\n    example: 'Pass#123',\n  })\n  @MinLength(8, {\n    message: 'password too short',\n  })\n  @MaxLength(20, {\n    message: 'password too long',\n  })\n  @Matches(/((?=.*\\d)|(?=.*\\W+))(?![.\\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {\n    message: 'password too weak',\n  })\n  @IsNotEmpty()\n  readonly password: string;\n\n  @ApiProperty({\n    description: 'Repeat same value as in password field',\n    example: 'Pass#123',\n  })\n  @Match('password')\n  @IsNotEmpty()\n  readonly passwordConfirm: string;\n}\n"
  },
  {
    "path": "src/auth/guards/jwt-auth.guard.ts",
    "content": "import {\n  CanActivate,\n  ExecutionContext,\n  Inject,\n  Injectable,\n  UnauthorizedException,\n} from '@nestjs/common';\nimport { ConfigType } from '@nestjs/config';\nimport { Reflector } from '@nestjs/core';\nimport { JwtService } from '@nestjs/jwt';\nimport { Request } from 'express';\n\nimport jwtConfig from '../../common/config/jwt.config';\nimport { REQUEST_USER_KEY } from '../../common/constants';\nimport { ActiveUserData } from '../../common/interfaces/active-user-data.interface';\nimport { RedisService } from '../../redis/redis.service';\n\n@Injectable()\nexport class JwtAuthGuard implements CanActivate {\n  constructor(\n    @Inject(jwtConfig.KEY)\n    private readonly jwtConfiguration: ConfigType<typeof jwtConfig>,\n    private readonly jwtService: JwtService,\n    private readonly redisService: RedisService,\n    private reflector: Reflector,\n  ) {}\n\n  async canActivate(context: ExecutionContext): Promise<boolean> {\n    const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [\n      context.getHandler(),\n      context.getClass(),\n    ]);\n    if (isPublic) {\n      return true;\n    }\n\n    const request = context.switchToHttp().getRequest();\n    const token = this.getToken(request);\n    if (!token) {\n      throw new UnauthorizedException('Authorization token is required');\n    }\n\n    try {\n      const payload = await this.jwtService.verifyAsync<ActiveUserData>(\n        token,\n        this.jwtConfiguration,\n      );\n\n      const isValidToken = await this.redisService.validate(\n        `user-${payload.id}`,\n        payload.tokenId,\n      );\n      if (!isValidToken) {\n        throw new UnauthorizedException('Authorization token is not valid');\n      }\n\n      request[REQUEST_USER_KEY] = payload;\n    } catch (error) {\n      throw new UnauthorizedException(error.message);\n    }\n\n    return true;\n  }\n\n  private getToken(request: Request) {\n    const [_, token] = request.headers.authorization?.split(' ') ?? [];\n    return token;\n  }\n}\n"
  },
  {
    "path": "src/common/config/app.config.ts",
    "content": "import { registerAs } from '@nestjs/config';\n\nexport default registerAs('app', () => {\n  return {\n    nodeEnv: process.env.NODE_ENV || 'development',\n    port: parseInt(process.env.PORT, 10) || 3000,\n  };\n});\n"
  },
  {
    "path": "src/common/config/database.config.ts",
    "content": "import { registerAs } from '@nestjs/config';\n\nexport default registerAs('database', () => {\n  return {\n    type: 'mysql',\n    host: process.env.DB_HOST || 'localhost',\n    port: parseInt(process.env.DB_PORT, 10) || 3306,\n    username: process.env.DB_USER,\n    password: process.env.DB_PASSWORD,\n    name: process.env.DB_NAME,\n  };\n});\n"
  },
  {
    "path": "src/common/config/jwt.config.ts",
    "content": "import { registerAs } from '@nestjs/config';\n\nexport default registerAs('jwt', () => {\n  return {\n    secret: process.env.JWT_SECRET,\n    accessTokenTtl: process.env.JWT_ACCESS_TOKEN_TTL,\n  };\n});\n"
  },
  {
    "path": "src/common/config/redis.config.ts",
    "content": "import { registerAs } from '@nestjs/config';\n\nexport default registerAs('redis', () => {\n  return {\n    host: process.env.REDIS_HOST,\n    port: parseInt(process.env.REDIS_PORT, 10) || 6379,\n    db: parseInt(process.env.REDIS_DATABASE, 10),\n    keyPrefix: process.env.REDIS_KEY_PREFIX + ':',\n    ...(process.env.REDIS_USERNAME && {\n      username: process.env.REDIS_USERNAME,\n    }),\n    ...(process.env.REDIS_PASSWORD && {\n      password: process.env.REDIS_PASSWORD,\n    }),\n  };\n});\n"
  },
  {
    "path": "src/common/config/swagger.config.ts",
    "content": "import { registerAs } from '@nestjs/config';\n\nexport default registerAs('swagger', () => {\n  return {\n    siteTitle: process.env.SWAGGER_SITE_TITLE,\n    docTitle: process.env.SWAGGER_DOC_TITLE,\n    docDescription: process.env.SWAGGER_DOC_DESCRIPTION,\n    docVersion: process.env.SWAGGER_DOC_VERSION,\n  };\n});\n"
  },
  {
    "path": "src/common/constants/index.ts",
    "content": "export const REQUEST_USER_KEY = 'user';\n"
  },
  {
    "path": "src/common/decorators/active-user.decorator.ts",
    "content": "import { createParamDecorator, ExecutionContext } from '@nestjs/common';\n\nimport { REQUEST_USER_KEY } from '../constants';\nimport { ActiveUserData } from '../interfaces/active-user-data.interface';\n\nexport const ActiveUser = createParamDecorator(\n  (field: keyof ActiveUserData | undefined, ctx: ExecutionContext) => {\n    const request = ctx.switchToHttp().getRequest();\n    const user: ActiveUserData | undefined = request[REQUEST_USER_KEY];\n    return field ? user?.[field] : user;\n  },\n);\n"
  },
  {
    "path": "src/common/decorators/match.decorator.ts",
    "content": "import {\n  registerDecorator,\n  ValidationArguments,\n  ValidationOptions,\n  ValidatorConstraint,\n  ValidatorConstraintInterface,\n} from 'class-validator';\n\nexport function Match(property: string, validationOptions?: ValidationOptions) {\n  return (object: any, propertyName: string) => {\n    registerDecorator({\n      target: object.constructor,\n      propertyName,\n      options: validationOptions,\n      constraints: [property],\n      validator: MatchConstraint,\n    });\n  };\n}\n\n@ValidatorConstraint({ name: 'Match' })\nexport class MatchConstraint implements ValidatorConstraintInterface {\n  validate(value: any, args: ValidationArguments) {\n    const [relatedPropertyName] = args.constraints;\n    const relatedValue = (args.object as any)[relatedPropertyName];\n    return value === relatedValue;\n  }\n  defaultMessage(args: ValidationArguments) {\n    return args.property + ' must match ' + args.constraints[0];\n  }\n}\n"
  },
  {
    "path": "src/common/decorators/public.decorator.ts",
    "content": "import { SetMetadata, CustomDecorator } from '@nestjs/common';\n\nexport const Public = (): CustomDecorator => SetMetadata('isPublic', true);\n"
  },
  {
    "path": "src/common/enums/environment.enum.ts",
    "content": "export enum Environment {\n  Development = 'development',\n  Production = 'production',\n  Test = 'test',\n}\n"
  },
  {
    "path": "src/common/enums/error-codes.enum.ts",
    "content": "export enum MysqlErrorCode {\n  UniqueViolation = 'ER_DUP_ENTRY',\n}\n"
  },
  {
    "path": "src/common/interceptors/transform.interceptor.ts",
    "content": "import {\n  CallHandler,\n  ExecutionContext,\n  Injectable,\n  NestInterceptor,\n} from '@nestjs/common';\nimport { instanceToPlain } from 'class-transformer';\nimport { map, Observable } from 'rxjs';\n\n@Injectable()\nexport class TransformInterceptor implements NestInterceptor {\n  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {\n    return next.handle().pipe(map((data) => instanceToPlain(data)));\n  }\n}\n"
  },
  {
    "path": "src/common/interfaces/active-user-data.interface.ts",
    "content": "export interface ActiveUserData {\n  id: string;\n  email: string;\n  tokenId: string;\n}\n"
  },
  {
    "path": "src/common/validation/env.validation.ts",
    "content": "import { plainToInstance } from 'class-transformer';\nimport {\n  IsEnum,\n  IsNotEmpty,\n  IsNumber,\n  IsOptional,\n  IsString,\n  validateSync,\n} from 'class-validator';\n\nimport { Environment } from '../enums/environment.enum';\n\nclass EnvironmentVariables {\n  @IsEnum(Environment)\n  NODE_ENV: Environment;\n\n  @IsNumber()\n  @IsNotEmpty()\n  PORT: number;\n\n  @IsString()\n  @IsNotEmpty()\n  DB_HOST: string;\n\n  @IsNumber()\n  @IsNotEmpty()\n  DB_PORT: number;\n\n  @IsString()\n  @IsNotEmpty()\n  DB_USER: string;\n\n  @IsString()\n  @IsNotEmpty()\n  DB_PASSWORD: string;\n\n  @IsString()\n  @IsNotEmpty()\n  DB_NAME: string;\n\n  @IsString()\n  @IsNotEmpty()\n  REDIS_HOST: string;\n\n  @IsNumber()\n  @IsNotEmpty()\n  REDIS_PORT: number;\n\n  @IsString()\n  @IsOptional()\n  REDIS_USERNAME: string;\n\n  @IsString()\n  @IsOptional()\n  REDIS_PASSWORD: string;\n\n  @IsNumber()\n  @IsNotEmpty()\n  REDIS_DATABASE: number;\n\n  @IsString()\n  @IsNotEmpty()\n  REDIS_KEY_PREFIX: string;\n\n  @IsString()\n  @IsNotEmpty()\n  JWT_SECRET: string;\n\n  @IsNotEmpty()\n  @IsNumber()\n  JWT_ACCESS_TOKEN_TTL: number;\n\n  @IsString()\n  @IsNotEmpty()\n  SWAGGER_SITE_TITLE: string;\n\n  @IsString()\n  @IsNotEmpty()\n  SWAGGER_DOC_TITLE: string;\n\n  @IsString()\n  @IsNotEmpty()\n  SWAGGER_DOC_DESCRIPTION: string;\n\n  @IsString()\n  @IsNotEmpty()\n  SWAGGER_DOC_VERSION: string;\n}\n\nexport function validate(config: Record<string, unknown>) {\n  const validatedConfig = plainToInstance(EnvironmentVariables, config, {\n    enableImplicitConversion: true,\n  });\n  const errors = validateSync(validatedConfig, {\n    skipMissingProperties: false,\n  });\n\n  let errorMessage = errors\n    .map((message) => message.constraints[Object.keys(message.constraints)[0]])\n    .join('\\n');\n\n  const COLOR = {\n    reset: '\\x1b[0m',\n    bright: '\\x1b[1m',\n    fgRed: '\\x1b[31m',\n  };\n\n  errorMessage = `${COLOR.fgRed}${COLOR.bright}${errorMessage}${COLOR.reset}`;\n\n  if (errors.length > 0) {\n    throw new Error(errorMessage);\n  }\n\n  return validatedConfig;\n}\n"
  },
  {
    "path": "src/database/database.module.ts",
    "content": "import { Module } from '@nestjs/common';\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport { TypeOrmModule } from '@nestjs/typeorm';\n\n@Module({\n  imports: [\n    TypeOrmModule.forRootAsync({\n      imports: [ConfigModule],\n      inject: [ConfigService],\n      useFactory: async (configService: ConfigService) => ({\n        type: 'mysql',\n        host: configService.get<string>('database.host'),\n        port: configService.get<number>('database.port'),\n        username: configService.get<string>('database.username'),\n        password: configService.get<string>('database.password'),\n        database: configService.get<string>('database.name'),\n        autoLoadEntities: true,\n        synchronize: true,\n        logging: false,\n      }),\n    }),\n  ],\n})\nexport class DatabaseModule {}\n"
  },
  {
    "path": "src/main.ts",
    "content": "import { ValidationPipe } from '@nestjs/common';\nimport { ConfigService } from '@nestjs/config';\nimport { NestFactory } from '@nestjs/core';\n\nimport { AppModule } from './app.module';\nimport { TransformInterceptor } from './common/interceptors/transform.interceptor';\nimport { setupSwagger } from './swagger';\n\nasync function bootstrap() {\n  const app = await NestFactory.create(AppModule);\n\n  setupSwagger(app);\n\n  app.useGlobalInterceptors(new TransformInterceptor());\n\n  app.useGlobalPipes(\n    new ValidationPipe({\n      transform: true,\n      whitelist: true,\n      forbidUnknownValues: true,\n      stopAtFirstError: true,\n      validateCustomDecorators: true,\n    }),\n  );\n\n  const configService = app.get(ConfigService);\n\n  const port = configService.get('PORT');\n\n  await app.listen(port, () => {\n    console.log(`Application running at ${port}`);\n  });\n}\n\nbootstrap();\n"
  },
  {
    "path": "src/metadata.ts",
    "content": "/* eslint-disable */\nexport default async () => {\n  const t = {\n    ['./users/entities/user.entity']: await import(\n      './users/entities/user.entity'\n    ),\n  };\n  return {\n    '@nestjs/swagger': {\n      models: [\n        [\n          import('./users/entities/user.entity'),\n          {\n            User: {\n              id: { required: true, type: () => String },\n              email: { required: true, type: () => String },\n              createdAt: { required: true, type: () => Date },\n              updatedAt: { required: true, type: () => Date },\n            },\n          },\n        ],\n        [\n          import('./auth/dto/sign-in.dto'),\n          {\n            SignInDto: {\n              email: { required: true, type: () => String, maxLength: 255 },\n              password: {\n                required: true,\n                type: () => String,\n                minLength: 8,\n                maxLength: 20,\n                pattern:\n                  '/((?=.*\\\\d)|(?=.*\\\\W+))(?![.\\\\n])(?=.*[A-Z])(?=.*[a-z]).*$/',\n              },\n            },\n          },\n        ],\n        [\n          import('./auth/dto/sign-up.dto'),\n          {\n            SignUpDto: {\n              email: { required: true, type: () => String, maxLength: 255 },\n              password: {\n                required: true,\n                type: () => String,\n                minLength: 8,\n                maxLength: 20,\n                pattern:\n                  '/((?=.*\\\\d)|(?=.*\\\\W+))(?![.\\\\n])(?=.*[A-Z])(?=.*[a-z]).*$/',\n              },\n              passwordConfirm: { required: true, type: () => String },\n            },\n          },\n        ],\n      ],\n      controllers: [\n        [\n          import('./app.controller'),\n          { AppController: { getHello: { type: String } } },\n        ],\n        [\n          import('./auth/auth.controller'),\n          { AuthController: { signUp: {}, signIn: {}, signOut: {} } },\n        ],\n        [\n          import('./users/users.controller'),\n          {\n            UsersController: {\n              getMe: { type: t['./users/entities/user.entity'].User },\n            },\n          },\n        ],\n      ],\n    },\n  };\n};\n"
  },
  {
    "path": "src/redis/redis.constants.ts",
    "content": "export const IORedisKey = 'IORedis';\n"
  },
  {
    "path": "src/redis/redis.module.ts",
    "content": "import { Global, Module, OnApplicationShutdown, Scope } from '@nestjs/common';\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport { ModuleRef } from '@nestjs/core';\nimport { Redis } from 'ioredis';\n\nimport { IORedisKey } from './redis.constants';\nimport { RedisService } from './redis.service';\n\n@Global()\n@Module({\n  imports: [ConfigModule],\n  providers: [\n    {\n      provide: IORedisKey,\n      useFactory: async (configService: ConfigService) => {\n        return new Redis(configService.get('redis'));\n      },\n      inject: [ConfigService],\n    },\n    RedisService,\n  ],\n  exports: [RedisService],\n})\nexport class RedisModule implements OnApplicationShutdown {\n  constructor(private readonly moduleRef: ModuleRef) {}\n\n  async onApplicationShutdown(signal?: string): Promise<void> {\n    return new Promise<void>((resolve) => {\n      const redis = this.moduleRef.get(IORedisKey);\n      redis.quit();\n      redis.on('end', () => {\n        resolve();\n      });\n    });\n  }\n}\n"
  },
  {
    "path": "src/redis/redis.service.ts",
    "content": "import { Inject, Injectable } from '@nestjs/common';\nimport { Redis } from 'ioredis';\n\nimport { IORedisKey } from './redis.constants';\n\n@Injectable()\nexport class RedisService {\n  constructor(\n    @Inject(IORedisKey)\n    private readonly redisClient: Redis,\n  ) {}\n\n  async getKeys(pattern?: string): Promise<string[]> {\n    return await this.redisClient.keys(pattern);\n  }\n\n  async insert(key: string, value: string | number): Promise<void> {\n    await this.redisClient.set(key, value);\n  }\n\n  async get(key: string): Promise<string> {\n    return this.redisClient.get(key);\n  }\n\n  async delete(key: string): Promise<void> {\n    await this.redisClient.del(key);\n  }\n\n  async validate(key: string, value: string): Promise<boolean> {\n    const storedValue = await this.redisClient.get(key);\n    return storedValue === value;\n  }\n}\n"
  },
  {
    "path": "src/swagger.ts",
    "content": "import { INestApplication } from '@nestjs/common';\nimport { ConfigService } from '@nestjs/config';\nimport {\n  DocumentBuilder,\n  SwaggerCustomOptions,\n  SwaggerDocumentOptions,\n  SwaggerModule,\n} from '@nestjs/swagger';\n\nimport metadata from './metadata';\n\nexport const setupSwagger = async (app: INestApplication) => {\n  const configService = app.get(ConfigService);\n  const swaggerConfig = configService.get('swagger');\n\n  const config = new DocumentBuilder()\n    .setTitle(swaggerConfig.docTitle)\n    .setDescription(swaggerConfig.docDescription)\n    .setVersion(swaggerConfig.docVersion)\n    .addBearerAuth()\n    .build();\n\n  const options: SwaggerDocumentOptions = {\n    operationIdFactory: (controllerKey: string, methodKey: string) => methodKey,\n  };\n\n  await SwaggerModule.loadPluginMetadata(metadata);\n\n  const document = SwaggerModule.createDocument(app, config, options);\n\n  const customOptions: SwaggerCustomOptions = {\n    swaggerOptions: {\n      persistAuthorization: true,\n      // defaultModelsExpandDepth: -1,\n    },\n    customSiteTitle: swaggerConfig.siteTitle,\n  };\n\n  SwaggerModule.setup('docs', app, document, customOptions);\n};\n"
  },
  {
    "path": "src/users/entities/user.entity.ts",
    "content": "import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';\nimport { Exclude } from 'class-transformer';\nimport {\n  Column,\n  CreateDateColumn,\n  Entity,\n  PrimaryGeneratedColumn,\n  UpdateDateColumn,\n} from 'typeorm';\n\n@Entity({\n  name: 'users',\n})\nexport class User {\n  @ApiProperty({\n    description: 'ID of user',\n    example: '89c018cc-8a77-4dbd-94e1-dbaa710a2a9c',\n  })\n  @PrimaryGeneratedColumn('uuid')\n  id: string;\n\n  @ApiProperty({ description: 'Email of user', example: 'atest@email.com' })\n  @Column({ unique: true })\n  email: string;\n\n  @ApiHideProperty()\n  @Column()\n  @Exclude({ toPlainOnly: true })\n  password: string;\n\n  @ApiProperty({ description: 'Created date of user' })\n  @CreateDateColumn({ name: 'created_at' })\n  createdAt: Date;\n\n  @ApiProperty({ description: 'Updated date of user' })\n  @UpdateDateColumn({ name: 'updated_at' })\n  updatedAt: Date;\n}\n"
  },
  {
    "path": "src/users/users.controller.ts",
    "content": "import { Controller, Get } from '@nestjs/common';\nimport {\n  ApiBearerAuth,\n  ApiOkResponse,\n  ApiTags,\n  ApiUnauthorizedResponse,\n} from '@nestjs/swagger';\n\nimport { ActiveUser } from '../common/decorators/active-user.decorator';\nimport { User } from './entities/user.entity';\nimport { UsersService } from './users.service';\n\n@ApiTags('users')\n@Controller('users')\nexport class UsersController {\n  constructor(private readonly usersService: UsersService) {}\n\n  @ApiUnauthorizedResponse({ description: 'Unauthorized' })\n  @ApiOkResponse({ description: \"Get logged in user's details\", type: User })\n  @ApiBearerAuth()\n  @Get('me')\n  async getMe(@ActiveUser('id') userId: string): Promise<User> {\n    return this.usersService.getMe(userId);\n  }\n}\n"
  },
  {
    "path": "src/users/users.module.ts",
    "content": "import { Module } from '@nestjs/common';\nimport { TypeOrmModule } from '@nestjs/typeorm';\n\nimport { User } from './entities/user.entity';\nimport { UsersController } from './users.controller';\nimport { UsersService } from './users.service';\n\n@Module({\n  imports: [TypeOrmModule.forFeature([User])],\n  controllers: [UsersController],\n  providers: [UsersService],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "src/users/users.service.ts",
    "content": "import { BadRequestException, Injectable } from '@nestjs/common';\nimport { InjectRepository } from '@nestjs/typeorm';\nimport { Repository } from 'typeorm';\n\nimport { User } from './entities/user.entity';\n\n@Injectable()\nexport class UsersService {\n  constructor(\n    @InjectRepository(User)\n    private readonly userRepository: Repository<User>,\n  ) {}\n\n  async getMe(userId: string): Promise<User> {\n    const user = await this.userRepository.findOne({\n      where: {\n        id: userId,\n      },\n    });\n    if (!user) {\n      throw new BadRequestException('User not found');\n    }\n\n    return user;\n  }\n}\n"
  },
  {
    "path": "test/e2e/app.e2e-spec.ts",
    "content": "import { HttpStatus } from '@nestjs/common';\nimport * as request from 'supertest';\nimport { DataSource } from 'typeorm';\nimport { Server } from 'http';\n\nimport { AppFactory } from '../factories/app.factory';\nimport { AuthService } from '../../src/auth/auth.service';\nimport { SignUpDto } from '../../src/auth/dto/sign-up.dto';\nimport { UserFactory } from '../factories/user.factory';\nimport { SignInDto } from '../../src/auth/dto/sign-in.dto';\n\ndescribe('App (e2e)', () => {\n  let app: AppFactory;\n  let server: Server;\n  let dataSource: DataSource;\n  let authService: AuthService;\n\n  beforeAll(async () => {\n    app = await AppFactory.new();\n    server = app.instance.getHttpServer();\n    dataSource = app.dbSource;\n    authService = app.instance.get(AuthService);\n  });\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  beforeEach(async () => {\n    await app.cleanupDB();\n  });\n\n  describe('AppModule', () => {\n    describe('GET /', () => {\n      it(\"should return 'Hello World'\", () => {\n        return request(app.instance.getHttpServer())\n          .get('/')\n          .expect(HttpStatus.OK)\n          .expect('Hello World!');\n      });\n    });\n  });\n\n  describe('AuthModule', () => {\n    describe('POST /auth/sign-up', () => {\n      it('should create a new user', async () => {\n        await new Promise((resolve) => setTimeout(resolve, 1));\n\n        const signUpDto: SignUpDto = {\n          email: 'atest@email.com',\n          password: 'Pass#123',\n          passwordConfirm: 'Pass#123',\n        };\n\n        return request(server)\n          .post('/auth/sign-up')\n          .send(signUpDto)\n          .expect(HttpStatus.CREATED);\n      });\n\n      it('should return 400 for invalid sign up fields', async () => {\n        await new Promise((resolve) => setTimeout(resolve, 1));\n\n        const signUpDto: SignUpDto = {\n          email: 'invalid-email',\n          password: 'Pass#123',\n          passwordConfirm: 'Pass#123',\n        };\n\n        return request(server)\n          .post('/auth/sign-up')\n          .send(signUpDto)\n          .expect(HttpStatus.BAD_REQUEST);\n      });\n\n      it('should return 409 if user already exists', async () => {\n        await UserFactory.new(dataSource).create({\n          email: 'atest@email.com',\n          password: 'Pass#123',\n        });\n\n        const signUpDto: SignUpDto = {\n          email: 'atest@email.com',\n          password: 'Pass#123',\n          passwordConfirm: 'Pass#123',\n        };\n\n        return request(server)\n          .post('/auth/sign-up')\n          .send(signUpDto)\n          .expect(HttpStatus.CONFLICT);\n      });\n    });\n\n    describe('POST /auth/sign-in', () => {\n      it('should sign in the user and return access token', async () => {\n        const email = 'atest@email.com';\n        const password = 'Pass#123';\n        await UserFactory.new(dataSource).create({\n          email,\n          password,\n        });\n\n        const signInDto: SignInDto = {\n          email,\n          password,\n        };\n\n        return request(server)\n          .post('/auth/sign-in')\n          .send(signInDto)\n          .expect(HttpStatus.OK)\n          .expect((res) => {\n            expect(res.body).toEqual({ accessToken: expect.any(String) });\n          });\n      });\n\n      it('should return 400 for invalid sign in fields', async () => {\n        const signInDto: SignInDto = {\n          email: 'atest@email.com',\n          password: '',\n        };\n\n        return request(server)\n          .post('/auth/sign-in')\n          .send(signInDto)\n          .expect(HttpStatus.BAD_REQUEST);\n      });\n    });\n\n    describe('POST /auth/sign-out', () => {\n      it('should sign out the user', async () => {\n        const user = await UserFactory.new(dataSource).create({\n          email: 'atest@email.com',\n          password: 'Pass#123',\n        });\n\n        const { accessToken } = await authService.generateAccessToken(user);\n\n        return request(server)\n          .post('/auth/sign-out')\n          .set('Authorization', `Bearer ${accessToken}`)\n          .expect(HttpStatus.OK);\n      });\n\n      it('should return 401 if not authorized', async () => {\n        return request(server)\n          .post('/auth/sign-out')\n          .expect(HttpStatus.UNAUTHORIZED);\n      });\n    });\n  });\n\n  describe('UsersModule', () => {\n    describe('GET /users/me', () => {\n      it('should return 401 unauthorized when no access token provided', () => {\n        return request(server).get('/users/me').expect(HttpStatus.UNAUTHORIZED);\n      });\n\n      it('should return user details when access token provided', async () => {\n        const user = await UserFactory.new(dataSource).create({\n          email: 'atest@email.com',\n          password: 'Pass#123',\n        });\n\n        const { accessToken } = await authService.generateAccessToken(user);\n\n        return request(server)\n          .get('/users/me')\n          .set('Authorization', `Bearer ${accessToken}`)\n          .expect(HttpStatus.OK)\n          .expect((res) => {\n            expect(res.body).toEqual(\n              expect.objectContaining({\n                id: user.id,\n                email: user.email,\n              }),\n            );\n          });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/e2e/jest-e2e.json",
    "content": "{\n  \"moduleFileExtensions\": [\"js\", \"json\", \"ts\"],\n  \"rootDir\": \"../../\",\n  \"testEnvironment\": \"node\",\n  \"testRegex\": \".e2e-spec.ts$\",\n  \"transform\": {\n    \"^.+\\\\.(t|j)s$\": \"ts-jest\"\n  },\n  \"modulePaths\": [\".\"]\n}\n"
  },
  {
    "path": "test/factories/app.factory.ts",
    "content": "import { INestApplication, Type, ValidationPipe } from '@nestjs/common';\nimport { Test } from '@nestjs/testing';\nimport { DataSource } from 'typeorm';\nimport { Redis } from 'ioredis';\nimport { getDataSourceToken } from '@nestjs/typeorm';\n\nimport { AppModule } from 'src/app.module';\nimport { TransformInterceptor } from '../../src/common/interceptors/transform.interceptor';\nimport { IORedisKey } from '../../src/redis/redis.constants';\n\nexport class AppFactory {\n  private constructor(\n    private readonly appInstance: INestApplication,\n    private readonly dataSource: DataSource,\n    private readonly redis: Redis,\n  ) {}\n\n  get instance() {\n    return this.appInstance;\n  }\n\n  get dbSource() {\n    return this.dataSource;\n  }\n\n  static async new() {\n    const module = await Test.createTestingModule({\n      imports: [AppModule],\n    }).compile();\n\n    const app = module.createNestApplication();\n\n    app.useGlobalInterceptors(new TransformInterceptor());\n\n    app.useGlobalPipes(\n      new ValidationPipe({\n        transform: true,\n        whitelist: true,\n        forbidUnknownValues: true,\n        stopAtFirstError: true,\n        validateCustomDecorators: true,\n      }),\n    );\n\n    await app.init();\n\n    const dataSource = module.get<DataSource>(\n      getDataSourceToken() as Type<DataSource>,\n    );\n\n    const redis = module.get<Redis>(IORedisKey);\n\n    return new AppFactory(app, dataSource, redis);\n  }\n\n  async close() {\n    await this.appInstance.close();\n  }\n\n  async cleanupDB() {\n    await this.redis.flushall();\n\n    const tables = this.dataSource.manager.connection.entityMetadatas.map(\n      (entity) => `${entity.tableName}`,\n    );\n    for (const table of tables) {\n      await this.dataSource.manager.connection.query(`DELETE FROM ${table};`);\n    }\n  }\n}\n"
  },
  {
    "path": "test/factories/user.factory.ts",
    "content": "import { DataSource, EntityManager } from 'typeorm';\nimport * as bcrypt from 'bcrypt';\n\nimport { User } from '../../src/users/entities/user.entity';\n\nexport class UserFactory {\n  private dataSource: DataSource;\n\n  static new(dataSource: DataSource) {\n    const factory = new UserFactory();\n    factory.dataSource = dataSource;\n    return factory;\n  }\n\n  async create(user: Partial<User> = {}) {\n    const userRepository = this.dataSource.getRepository(User);\n    const salt = await bcrypt.genSalt();\n    const password = await this.hashPassword(user.password, salt);\n    const payload = {\n      ...user,\n      password,\n    };\n    return userRepository.save(payload);\n  }\n\n  private hashPassword(password: string, salt: string) {\n    return bcrypt.hash(password, salt);\n  }\n}\n"
  },
  {
    "path": "test/unit/app.service.unit-spec.ts",
    "content": "import { Test, TestingModule } from '@nestjs/testing';\n\nimport { AppService } from '../../src/app.service';\n\ndescribe('AppService', () => {\n  let service: AppService;\n\n  beforeEach(async () => {\n    const moduleRef: TestingModule = await Test.createTestingModule({\n      providers: [AppService],\n    }).compile();\n\n    service = moduleRef.get<AppService>(AppService);\n  });\n\n  describe('getHello', () => {\n    it('should return \"Hello World!\"', () => {\n      const result = service.getHello();\n\n      expect(result).toEqual('Hello World!');\n    });\n  });\n});\n"
  },
  {
    "path": "test/unit/auth/auth.service.unit-spec.ts",
    "content": "import { Test } from '@nestjs/testing';\nimport { ConfigType } from '@nestjs/config';\nimport { JwtService } from '@nestjs/jwt';\nimport { Repository } from 'typeorm';\nimport { BadRequestException, ConflictException } from '@nestjs/common';\nimport { createMock } from '@golevelup/ts-jest';\nimport { getRepositoryToken } from '@nestjs/typeorm';\n\nimport { AuthService } from '../../../src/auth/auth.service';\nimport { BcryptService } from '../../../src/auth/bcrypt.service';\nimport { RedisService } from '../../../src/redis/redis.service';\nimport jwtConfig from '../../../src/common/config/jwt.config';\nimport { User } from '../../../src/users/entities/user.entity';\nimport { SignUpDto } from '../../../src/auth/dto/sign-up.dto';\nimport { MysqlErrorCode } from '../../../src/common/enums/error-codes.enum';\nimport { ActiveUserData } from '../../../src/common/interfaces/active-user-data.interface';\n\ndescribe('AuthService', () => {\n  let authService: AuthService;\n  let bcryptService: BcryptService;\n  let jwtService: JwtService;\n  let userRepository: Repository<User>;\n  let redisService: RedisService;\n  let jwtConfiguration: ConfigType<typeof jwtConfig>;\n\n  beforeEach(async () => {\n    const moduleRef = await Test.createTestingModule({\n      providers: [\n        AuthService,\n        { provide: BcryptService, useValue: createMock<BcryptService>() },\n        { provide: JwtService, useValue: createMock<JwtService>() },\n        { provide: RedisService, useValue: createMock<RedisService>() },\n        {\n          provide: getRepositoryToken(User),\n          useClass: Repository,\n        },\n        {\n          provide: jwtConfig.KEY,\n          useValue: jwtConfig.asProvider(),\n        },\n      ],\n    }).compile();\n\n    authService = moduleRef.get<AuthService>(AuthService);\n    bcryptService = moduleRef.get<BcryptService>(BcryptService);\n    jwtService = moduleRef.get<JwtService>(JwtService);\n    userRepository = moduleRef.get<Repository<User>>(getRepositoryToken(User));\n    redisService = moduleRef.get<RedisService>(RedisService);\n    jwtConfiguration = moduleRef.get<ConfigType<typeof jwtConfig>>(\n      jwtConfig.KEY,\n    );\n  });\n\n  describe('signUp', () => {\n    const signUpDto: SignUpDto = {\n      email: 'test@example.com',\n      password: 'password',\n      passwordConfirm: 'password',\n    };\n    let user: User;\n\n    beforeEach(() => {\n      user = new User();\n      user.email = signUpDto.email;\n      user.password = 'hashed_password';\n    });\n\n    it('should create a new user', async () => {\n      const saveSpy = jest\n        .spyOn(userRepository, 'save')\n        .mockResolvedValueOnce(user);\n      const hashSpy = jest\n        .spyOn(bcryptService, 'hash')\n        .mockResolvedValueOnce('hashed_password');\n\n      await authService.signUp(signUpDto);\n\n      expect(hashSpy).toHaveBeenCalledWith(signUpDto.password);\n      expect(saveSpy).toHaveBeenCalledWith(user);\n    });\n\n    it('should throw a ConflictException if a user with the same email already exists', async () => {\n      const saveSpy = jest\n        .spyOn(userRepository, 'save')\n        .mockRejectedValueOnce({ code: MysqlErrorCode.UniqueViolation });\n\n      await expect(authService.signUp(signUpDto)).rejects.toThrowError(\n        new ConflictException(`User [${signUpDto.email}] already exist`),\n      );\n\n      expect(saveSpy).toHaveBeenCalledWith(user);\n    });\n\n    it('should rethrow any other error that occurs during signup', async () => {\n      const saveSpy = jest\n        .spyOn(userRepository, 'save')\n        .mockRejectedValueOnce(new Error('Unexpected error'));\n\n      await expect(authService.signUp(signUpDto)).rejects.toThrowError(\n        new Error('Unexpected error'),\n      );\n\n      expect(saveSpy).toHaveBeenCalledWith(user);\n    });\n  });\n\n  describe('signIn', () => {\n    it('should sign in a user and return an access token', async () => {\n      const signInDto = {\n        email: 'johndoe@example.com',\n        password: 'password',\n      };\n\n      const user = new User();\n      user.id = '123';\n      user.email = signInDto.email;\n      user.password = 'encryptedPassword';\n\n      const encryptedPassword = 'encryptedPassword';\n      const comparedPassword = true;\n      const tokenId = expect.any(String);\n\n      jest.spyOn(userRepository, 'findOne').mockResolvedValue(user);\n      jest.spyOn(bcryptService, 'compare').mockResolvedValue(comparedPassword);\n      jest\n        .spyOn(authService, 'generateAccessToken')\n        .mockResolvedValue({ accessToken: 'accessToken' });\n\n      const result = await authService.signIn(signInDto);\n\n      expect(result).toEqual({ accessToken: 'accessToken' });\n      expect(userRepository.findOne).toHaveBeenCalledWith({\n        where: { email: signInDto.email },\n      });\n      expect(bcryptService.compare).toHaveBeenCalledWith(\n        signInDto.password,\n        encryptedPassword,\n      );\n    });\n\n    it('should throw an error when email is invalid', async () => {\n      const signInDto = {\n        email: 'invalid-email',\n        password: 'Pass#123',\n      };\n      jest.spyOn(userRepository, 'findOne').mockResolvedValue(undefined);\n\n      await expect(authService.signIn(signInDto)).rejects.toThrow(\n        BadRequestException,\n      );\n\n      expect(userRepository.findOne).toHaveBeenCalledWith({\n        where: { email: signInDto.email },\n      });\n    });\n\n    it('should throw an error when password is invalid', async () => {\n      const signInDto = {\n        email: 'johndoe@example.com',\n        password: 'password',\n      };\n\n      const user = new User();\n      user.id = '123';\n      user.email = signInDto.email;\n      user.password = 'encryptedPassword';\n\n      jest.spyOn(userRepository, 'findOne').mockResolvedValue(user);\n      jest.spyOn(bcryptService, 'compare').mockResolvedValue(false);\n\n      await expect(authService.signIn(signInDto)).rejects.toThrow(\n        BadRequestException,\n      );\n\n      expect(userRepository.findOne).toHaveBeenCalledWith({\n        where: { email: signInDto.email },\n      });\n      expect(bcryptService.compare).toHaveBeenCalledWith(\n        signInDto.password,\n        user.password,\n      );\n    });\n  });\n\n  describe('signOut', () => {\n    it('should delete user token from Redis', async () => {\n      const userId = 'test-user-id';\n      const deleteSpy = jest\n        .spyOn(redisService, 'delete')\n        .mockResolvedValue(undefined);\n\n      await authService.signOut(userId);\n\n      expect(deleteSpy).toHaveBeenCalledWith(`user-${userId}`);\n    });\n  });\n\n  describe('generateAccessToken', () => {\n    it('should insert a token into Redis and return an access token', async () => {\n      const user = { id: '123', email: 'test@example.com' };\n      const tokenId = expect.any(String);\n      const accessToken = 'test-access-token';\n      (redisService.insert as any).mockResolvedValueOnce(undefined);\n      (jwtService.signAsync as any).mockResolvedValueOnce(accessToken);\n\n      const result = await authService.generateAccessToken(user);\n\n      expect(redisService.insert).toHaveBeenCalledWith(\n        `user-${user.id}`,\n        tokenId,\n      );\n      expect(jwtService.signAsync).toHaveBeenCalledWith(\n        { id: user.id, email: user.email, tokenId } as ActiveUserData,\n        {\n          secret: jwtConfiguration.secret,\n          expiresIn: jwtConfiguration.accessTokenTtl,\n        },\n      );\n      expect(result).toEqual({ accessToken });\n    });\n  });\n});\n"
  },
  {
    "path": "test/unit/auth/bcrypt.service.unit-spec.ts",
    "content": "import { BcryptService } from '../../../src/auth/bcrypt.service';\n\ndescribe('BcryptService', () => {\n  let bcryptService: BcryptService;\n\n  beforeEach(() => {\n    bcryptService = new BcryptService();\n  });\n\n  describe('hash', () => {\n    it('should return a hashed string', async () => {\n      const data = 'password';\n\n      const result = await bcryptService.hash(data);\n\n      expect(result).not.toBe(data);\n      expect(result).toBeDefined();\n      expect(result).not.toBeNull();\n      expect(typeof result).toBe('string');\n    });\n  });\n\n  describe('compare', () => {\n    it('should return true if the data matches the encrypted string', async () => {\n      const data = 'password';\n      const encrypted =\n        '$2b$10$iUp/PtR8IlnyKFD5ZjP0X.DUg4.zFec3jr/XoMm9/rIXC0dzaRUmS';\n\n      const result = await bcryptService.compare(data, encrypted);\n\n      expect(result).toBe(true);\n    });\n\n    it('should return false if the data does not match the encrypted string', async () => {\n      const data = 'password';\n      const encrypted =\n        '$2b$10$iUp/PtR8IlnyKFD5ZjP0X.DUg4.zFec3jr/XoMm9/rIXC0dzaRUmS';\n\n      const result = await bcryptService.compare('wrong-password', encrypted);\n\n      expect(result).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "test/unit/auth/guards/jwt-auth.guard.unit-spec.ts",
    "content": "import { ExecutionContext, UnauthorizedException } from '@nestjs/common';\nimport { Test, TestingModule } from '@nestjs/testing';\nimport { JwtService } from '@nestjs/jwt';\nimport { Reflector } from '@nestjs/core';\nimport { createMock } from '@golevelup/ts-jest';\nimport { ConfigModule } from '@nestjs/config';\n\nimport { JwtAuthGuard } from '../../../../src/auth/guards/jwt-auth.guard';\nimport { RedisService } from '../../../../src/redis/redis.service';\nimport jwtConfig from '../../../../src/common/config/jwt.config';\n\ndescribe('JwtAuthGuard', () => {\n  let guard: JwtAuthGuard;\n  let redisService: RedisService;\n  let jwtService: JwtService;\n  let reflector: Reflector;\n  let mockExecutionContext: ExecutionContext;\n\n  beforeEach(async () => {\n    const moduleRef: TestingModule = await Test.createTestingModule({\n      imports: [\n        ConfigModule.forRoot({\n          load: [jwtConfig],\n        }),\n      ],\n      providers: [\n        JwtAuthGuard,\n        {\n          provide: RedisService,\n          useValue: createMock<RedisService>(),\n        },\n        {\n          provide: JwtService,\n          useValue: createMock<JwtService>(),\n        },\n        {\n          provide: Reflector,\n          useValue: createMock<Reflector>(),\n        },\n      ],\n    }).compile();\n\n    guard = moduleRef.get<JwtAuthGuard>(JwtAuthGuard);\n    redisService = moduleRef.get<RedisService>(RedisService);\n    jwtService = moduleRef.get<JwtService>(JwtService);\n    reflector = moduleRef.get<Reflector>(Reflector);\n    mockExecutionContext = createMock<ExecutionContext>();\n  });\n\n  afterEach(() => {\n    jest.resetAllMocks();\n  });\n\n  it('should be defined', () => {\n    expect(guard).toBeDefined();\n  });\n\n  it('should allow access to public routes', async () => {\n    jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(true);\n\n    const result = await guard.canActivate(mockExecutionContext);\n\n    expect(result).toBe(true);\n  });\n\n  it('should not allow access without a token', async () => {\n    jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false);\n    jest.spyOn(guard as any, 'getToken').mockReturnValue(undefined);\n\n    await expect(guard.canActivate(mockExecutionContext)).rejects.toThrowError(\n      new UnauthorizedException('Authorization token is required'),\n    );\n  });\n\n  it('should not allow access with an invalid token', async () => {\n    jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false);\n    jest.spyOn(guard as any, 'getToken').mockReturnValue('invalid-token');\n    jest.spyOn(redisService, 'validate').mockResolvedValue(false);\n\n    await expect(guard.canActivate(mockExecutionContext)).rejects.toThrowError(\n      new UnauthorizedException('Authorization token is not valid'),\n    );\n  });\n\n  it('should allow access with a valid token', async () => {\n    const validToken = 'valid-token';\n    jest.spyOn(guard as any, 'getToken').mockReturnValue(validToken);\n    jest.spyOn(redisService, 'validate').mockResolvedValue(true);\n\n    const result = await guard.canActivate(mockExecutionContext);\n\n    expect(result).toBe(true);\n  });\n});\n"
  },
  {
    "path": "test/unit/jest-unit.json",
    "content": "{\n  \"moduleFileExtensions\": [\"js\", \"json\", \"ts\"],\n  \"rootDir\": \"../../\",\n  \"testEnvironment\": \"node\",\n  \"testRegex\": \".unit-spec.ts$\",\n  \"transform\": {\n    \"^.+\\\\.(t|j)s$\": \"ts-jest\"\n  },\n  \"modulePaths\": [\".\"]\n}\n"
  },
  {
    "path": "test/unit/redis/redis.service.unit-spec.ts",
    "content": "import { createMock } from '@golevelup/ts-jest';\nimport { Test } from '@nestjs/testing';\nimport { Redis } from 'ioredis';\n\nimport { IORedisKey } from '../../../src/redis/redis.constants';\nimport { RedisService } from '../../../src/redis/redis.service';\n\ndescribe('RedisService', () => {\n  let redisService: RedisService;\n  let redisClient: Redis;\n\n  beforeEach(async () => {\n    const moduleRef = await Test.createTestingModule({\n      providers: [\n        RedisService,\n        {\n          provide: IORedisKey,\n          useValue: createMock<Redis>(),\n        },\n      ],\n    }).compile();\n\n    redisService = moduleRef.get<RedisService>(RedisService);\n    redisClient = moduleRef.get<Redis>(IORedisKey);\n  });\n\n  afterEach(() => {\n    jest.resetAllMocks();\n  });\n\n  it('should call redisClient.keys with the provided pattern when getKeys is called', async () => {\n    const pattern = 'test*';\n    const expectedKeys = ['test1', 'test2'];\n    (redisClient.keys as any).mockResolvedValue(expectedKeys);\n\n    const result = await redisService.getKeys(pattern);\n\n    expect(redisClient.keys).toHaveBeenCalledWith(pattern);\n    expect(result).toEqual(expectedKeys);\n  });\n\n  it('should call redisClient.set with the provided key and value when insert is called', async () => {\n    const key = 'test-key';\n    const value = 'test-value';\n\n    await redisService.insert(key, value);\n\n    expect(redisClient.set).toHaveBeenCalledWith(key, value);\n  });\n\n  it('should call redisClient.get with the provided key when get is called', async () => {\n    const key = 'test-key';\n    const expectedValue = 'test-value';\n    (redisClient.get as any).mockResolvedValue(expectedValue);\n\n    const result = await redisService.get(key);\n\n    expect(redisClient.get).toHaveBeenCalledWith(key);\n    expect(result).toEqual(expectedValue);\n  });\n\n  it('should call redisClient.del with the provided key when delete is called', async () => {\n    const key = 'test-key';\n\n    await redisService.delete(key);\n\n    expect(redisClient.del).toHaveBeenCalledWith(key);\n  });\n\n  it('should return true if the stored value matches the provided value when validate is called', async () => {\n    const key = 'test-key';\n    const value = 'test-value';\n    (redisClient.get as any).mockResolvedValue(value);\n\n    const result = await redisService.validate(key, value);\n\n    expect(redisClient.get).toHaveBeenCalledWith(key);\n    expect(result).toBe(true);\n  });\n\n  it('should return false if the stored value does not match the provided value when validate is called', async () => {\n    const key = 'test-key';\n    const value = 'test-value';\n    (redisClient.get as any).mockResolvedValue('other-value');\n\n    const result = await redisService.validate(key, value);\n\n    expect(redisClient.get).toHaveBeenCalledWith(key);\n    expect(result).toBe(false);\n  });\n});\n"
  },
  {
    "path": "test/unit/users/users.service.unit-spec.ts",
    "content": "import { BadRequestException } from '@nestjs/common';\nimport { Test, TestingModule } from '@nestjs/testing';\nimport { getRepositoryToken } from '@nestjs/typeorm';\nimport { Repository } from 'typeorm';\n\nimport { User } from '../../../src/users/entities/user.entity';\nimport { UsersService } from '../../../src/users/users.service';\n\ndescribe('UsersService', () => {\n  let usersService: UsersService;\n  let userRepository: Repository<User>;\n\n  beforeEach(async () => {\n    const moduleRef: TestingModule = await Test.createTestingModule({\n      providers: [\n        UsersService,\n        {\n          provide: getRepositoryToken(User),\n          useClass: Repository,\n        },\n      ],\n    }).compile();\n\n    usersService = moduleRef.get<UsersService>(UsersService);\n    userRepository = moduleRef.get<Repository<User>>(getRepositoryToken(User));\n  });\n\n  describe('getMe', () => {\n    it('should return a user with the specified ID', async () => {\n      const userId = '123';\n      const user = new User();\n      user.id = userId;\n      jest.spyOn(userRepository, 'findOne').mockResolvedValue(user);\n\n      const result = await usersService.getMe(userId);\n\n      expect(result).toEqual(user);\n    });\n\n    it('should throw a BadRequestException if user is not found', async () => {\n      const userId = '123';\n      jest.spyOn(userRepository, 'findOne').mockResolvedValue(undefined);\n\n      await expect(usersService.getMe(userId)).rejects.toThrow(\n        BadRequestException,\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "tsconfig.build.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"exclude\": [\"node_modules\", \"test\", \"dist\", \"**/*spec.ts\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"target\": \"es2017\",\n    \"sourceMap\": true,\n    \"outDir\": \"./dist\",\n    \"baseUrl\": \"./\",\n    \"incremental\": true,\n    \"skipLibCheck\": true,\n    \"strictNullChecks\": false,\n    \"noImplicitAny\": false,\n    \"strictBindCallApply\": false,\n    \"forceConsistentCasingInFileNames\": false,\n    \"noFallthroughCasesInSwitch\": false\n  }\n}\n"
  }
]