main d67cb13bbbe6 cached
69 files
67.1 KB
18.7k tokens
70 symbols
1 requests
Download .txt
Repository: anilahir/nestjs-authentication-and-authorization
Branch: main
Commit: d67cb13bbbe6
Files: 69
Total size: 67.1 KB

Directory structure:
gitextract_hglp_yas/

├── .dockerignore
├── .eslintrc.js
├── .github/
│   ├── actions/
│   │   └── setvars/
│   │       └── action.yml
│   ├── dependabot.yml
│   ├── variables/
│   │   └── myvars.env
│   └── workflows/
│       ├── ci.yml
│       └── dependabot-auto-merge.yml
├── .gitignore
├── .husky/
│   ├── pre-commit
│   └── pre-push
├── .lintstagedrc.json
├── .prettierignore
├── .prettierrc
├── .swcrc
├── Dockerfile
├── LICENSE
├── README.md
├── docker-compose-test.yml
├── docker-compose.yml
├── nest-cli.json
├── package.json
├── src/
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   ├── auth/
│   │   ├── auth.controller.ts
│   │   ├── auth.module.ts
│   │   ├── auth.service.ts
│   │   ├── bcrypt.service.ts
│   │   ├── dto/
│   │   │   ├── sign-in.dto.ts
│   │   │   └── sign-up.dto.ts
│   │   └── guards/
│   │       └── jwt-auth.guard.ts
│   ├── common/
│   │   ├── config/
│   │   │   ├── app.config.ts
│   │   │   ├── database.config.ts
│   │   │   ├── jwt.config.ts
│   │   │   ├── redis.config.ts
│   │   │   └── swagger.config.ts
│   │   ├── constants/
│   │   │   └── index.ts
│   │   ├── decorators/
│   │   │   ├── active-user.decorator.ts
│   │   │   ├── match.decorator.ts
│   │   │   └── public.decorator.ts
│   │   ├── enums/
│   │   │   ├── environment.enum.ts
│   │   │   └── error-codes.enum.ts
│   │   ├── interceptors/
│   │   │   └── transform.interceptor.ts
│   │   ├── interfaces/
│   │   │   └── active-user-data.interface.ts
│   │   └── validation/
│   │       └── env.validation.ts
│   ├── database/
│   │   └── database.module.ts
│   ├── main.ts
│   ├── metadata.ts
│   ├── redis/
│   │   ├── redis.constants.ts
│   │   ├── redis.module.ts
│   │   └── redis.service.ts
│   ├── swagger.ts
│   └── users/
│       ├── entities/
│       │   └── user.entity.ts
│       ├── users.controller.ts
│       ├── users.module.ts
│       └── users.service.ts
├── test/
│   ├── e2e/
│   │   ├── app.e2e-spec.ts
│   │   └── jest-e2e.json
│   ├── factories/
│   │   ├── app.factory.ts
│   │   └── user.factory.ts
│   └── unit/
│       ├── app.service.unit-spec.ts
│       ├── auth/
│       │   ├── auth.service.unit-spec.ts
│       │   ├── bcrypt.service.unit-spec.ts
│       │   └── guards/
│       │       └── jwt-auth.guard.unit-spec.ts
│       ├── jest-unit.json
│       ├── redis/
│       │   └── redis.service.unit-spec.ts
│       └── users/
│           └── users.service.unit-spec.ts
├── tsconfig.build.json
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
# Versioning and metadata
.vscode
.git
.gitignore
.dockerignore

# Build dependencies
dist
node_modules
coverage

# Environment (contains sensitive data)
.env

# Files not required for production
Dockerfile
README.md


================================================
FILE: .eslintrc.js
================================================
module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: 'tsconfig.json',
    tsconfigRootDir: __dirname,
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint/eslint-plugin'],
  extends: [
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
  ],
  root: true,
  env: {
    node: true,
    jest: true,
  },
  ignorePatterns: ['.eslintrc.js'],
  rules: {
    '@typescript-eslint/interface-name-prefix': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-unused-vars': 'off',
  },
};


================================================
FILE: .github/actions/setvars/action.yml
================================================
name: 'Set environment variables'
description: 'Configures environment variables for a workflow'
inputs:
  varFilePath:
    description: 'File path to variable file or directory. Defaults to ./.github/variables/* if none specified and runs against each file in that directory.'
    required: false
    default: ./.github/variables/*
runs:
  using: "composite"
  steps:
    - run: |
        sed "" ${{ inputs.varFilePath }} >> $GITHUB_ENV
      shell: bash

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: npm
    directory: '/'
    schedule:
      interval: 'daily'
    open-pull-requests-limit: 10
    labels:
      - 'dependencies'
  - package-ecosystem: 'github-actions'
    directory: '/'
    schedule:
      interval: 'daily'


================================================
FILE: .github/variables/myvars.env
================================================
NODE_ENV=test
PORT=3000

DB_HOST=localhost
DB_PORT=3306
DB_USER=admin
DB_PASSWORD=test123!
DB_NAME=nestjs-auth

REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_USERNAME=
REDIS_PASSWORD=
REDIS_DATABASE=1
REDIS_KEY_PREFIX=nestjs-auth

JWT_SECRET=your-secret-key
JWT_ACCESS_TOKEN_TTL=86400

SWAGGER_SITE_TITLE=The NestJs Authentication API
SWAGGER_DOC_TITLE=NestJs Authentication
SWAGGER_DOC_DESCRIPTION=The NestJs Authentication API
SWAGGER_DOC_VERSION=1.0

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI Testing

on:
  push:
    branches: [main]
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x, 20.x]
        test: [nestjs-authentication-and-authorization]

    steps:
      - uses: actions/checkout@v6
      - name: Use NodeJS ${{ matrix.node-version }}
        uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}

      - name: Setup npm
        run: npm install -g npm

      - name: Setup Nodejs with npm caching
        uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}
          cache: npm

      - name: Install dependencies
        run: npm i

      - name: Run unit test
        run: npm run test:unit

  e2e-test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x, 20.x]
    needs: [unit-tests]
    steps:
      - uses: actions/checkout@v6
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}

      - name: Setup npm
        run: npm install -g npm

      - name: Setup Nodejs with npm caching
        uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}
          cache: npm

      - name: Set Environment Variables
        uses: ./.github/actions/setvars
        with:
          varFilePath: ./.github/variables/myvars.env

      - name: Start Docker-Compose
        run: docker-compose -f docker-compose-test.yml up -d

      - name: Install dependencies
        run: npm i

      - name: Run tests
        run: npm run test:e2e

      - name: Stop Docker-Compose
        run: docker-compose -f docker-compose-test.yml down


================================================
FILE: .github/workflows/dependabot-auto-merge.yml
================================================
name: Dependabot auto-merge PRs if CI Testing succeeds

on:
  pull_request_target:
  workflow_run:
    workflows: ['CI Testing']
    types:
      - completed

permissions:
  pull-requests: write
  contents: write

jobs:
  dependabot:
    runs-on: ubuntu-latest
    if: ${{ github.actor == 'dependabot[bot]' }} && ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
      - name: Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v3.1.0
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Enable auto-merge for Dependabot PRs
        # if: contains(steps.metadata.outputs.dependency-names, 'my-dependency') && steps.metadata.outputs.update-type == 'version-update:semver-patch'
        run: gh pr merge --auto --rebase --delete-branch "${{ github.event.pull_request.html_url }}"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
# compiled output
/dist
/node_modules

# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# OS
.DS_Store

# Tests
/coverage
/.nyc_output

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# Environment
.env


================================================
FILE: .husky/pre-commit
================================================
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged


================================================
FILE: .husky/pre-push
================================================
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run test:unit && npm run test:e2e


================================================
FILE: .lintstagedrc.json
================================================
{
  "**/*.{js,jsx,ts,tsx}": [
    "eslint --fix",
    "prettier --config ./.prettierrc --write"
  ],
  "**/*.{css,scss,md,html,json}": ["prettier --config ./.prettierrc --write"]
}


================================================
FILE: .prettierignore
================================================
dist/
node_modules/


================================================
FILE: .prettierrc
================================================
{
  "singleQuote": true,
  "trailingComma": "all"
}

================================================
FILE: .swcrc
================================================
{
  "$schema": "https://json.schemastore.org/swcrc",
  "sourceMaps": true,
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "decorators": true,
      "dynamicImport": true
    },
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": true
    },
    "target": "es2017",
    "keepClassNames": true,
    "baseUrl": "./"
  },
  "module": {
    "type": "commonjs",
    "strictMode": true
  }
}


================================================
FILE: Dockerfile
================================================
###################
# BUILD FOR LOCAL DEVELOPMENT
###################

FROM node:18-alpine As development

# Create app directory
WORKDIR /usr/src/app

# Copy application dependency manifests to the container image.
# A wildcard is used to ensure copying both package.json AND package-lock.json (when available).
# Copying this first prevents re-running npm install on every code change.
COPY --chown=node:node package*.json ./

# Install app dependencies using the `npm ci` command instead of `npm install`
RUN npm ci

# Bundle app source
COPY --chown=node:node . .

# Use the node user from the image (instead of the root user)
USER node

###################
# BUILD FOR PRODUCTION
###################

FROM node:18-alpine As build

WORKDIR /usr/src/app

COPY --chown=node:node package*.json ./

# In order to run `npm run build` we need access to the Nest CLI.
# The Nest CLI is a dev dependency,
# In the previous development stage we ran `npm ci` which installed all dependencies.
# So we can copy over the node_modules directory from the development image into this build image.
COPY --chown=node:node --from=development /usr/src/app/node_modules ./node_modules

COPY --chown=node:node . .

# Run the build command which creates the production bundle
RUN npm run build

# Set NODE_ENV environment variable
ENV NODE_ENV production

# Running `npm ci` removes the existing node_modules directory.
# Passing in --only=production ensures that only the production dependencies are installed.
# This ensures that the node_modules directory is as optimized as possible.
RUN npm ci --only=production && npm cache clean --force

USER node

###################
# PRODUCTION
###################

FROM node:18-alpine As production

# Copy the bundled code from the build stage to the production image
COPY --chown=node:node --from=build /usr/src/app/node_modules ./node_modules
COPY --chown=node:node --from=build /usr/src/app/dist ./dist

# Start the server using the production build
CMD [ "node", "dist/main.js" ]

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 Anil Ahir

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.


================================================
FILE: README.md
================================================
# NestJS Authentication

![Workflow Test](https://github.com/anilahir/nestjs-authentication-and-authorization/actions/workflows/ci.yml/badge.svg)
![Prettier](https://img.shields.io/badge/Code%20style-prettier-informational?logo=prettier&logoColor=white)
[![GPL v3 License](https://img.shields.io/badge/License-GPLv3-green.svg)](./LICENSE)
[![HitCount](https://hits.dwyl.com/anilahir/nestjs-authentication-and-authorization.svg)](https://hits.dwyl.com/anilahir/nestjs-authentication-and-authorization)

## Description

NestJS Authentication without Passport using Bcrypt, JWT and Redis

## Features

1. Register
2. Login
3. Show profile
4. Logout

## Technologies stack:

- JWT
- Bcrypt
- TypeORM + MySQL
- Redis
- Docker

## Setup

### 1. Install the required dependencies

```bash
$ npm install
```

### 2. Rename the .env.example filename to .env and set your local variables

```bash
$ mv .env.example .env
```

### 3. Start the application

```bash
# development
$ npm run start

# watch mode
$ npm run start:dev

# production mode
$ npm run start:prod
```

## Docker for development

```bash
# start the application
$ npm run docker:up

# stop the application
$ npm run docker:down
```

## Swagger documentation

- [localhost:3000/docs](http://localhost:3000/docs)

## References

- [NestJS Authentication without Passport](https://trilon.io/blog/nestjs-authentication-without-passport)
- [NestJS, Redis and Postgres local development with Docker Compose](https://www.tomray.dev/nestjs-docker-compose-postgres)

## Author

👤 **Anil Ahir**

- Twitter: [@anilahir220](https://twitter.com/anilahir220)
- Github: [@anilahir](https://github.com/anilahir)
- LinkedIn: [@anilahir](https://www.linkedin.com/in/anilahir)

## Show your support

Give a ⭐️ if this project helped you!

## Related projects

Explore more NestJS example projects:

[![GraphQL example](https://github-readme-stats.vercel.app/api/pin/?username=anilahir&repo=nestjs-graphql-demo)](https://github.com/anilahir/nestjs-graphql-demo)

## License

Release under the terms of [MIT](./LICENSE)


================================================
FILE: docker-compose-test.yml
================================================
services:
  mysql:
    image: mysql:8.0
    ports:
      - 3306:3306
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: 'true'
      MYSQL_USER: admin
      MYSQL_PASSWORD: test123!
      MYSQL_DATABASE: nestjs-auth
      TZ: 'utc'
    command: --default-authentication-plugin=mysql_native_password

  redis:
    image: redis:alpine
    ports:
      - 6379:6379



================================================
FILE: docker-compose.yml
================================================
services:
  nestjs-auth-api:
    container_name: nestjs-auth-api
    image: nestjs-auth-api
    restart: unless-stopped
    build:
      context: .
      dockerfile: Dockerfile
      target: development # Only will build development stage from our dockerfile
    volumes:
      - ./:/usr/src/app
    ports:
      - ${PORT}:${PORT}
    networks:
      - nestjs-auth-intranet
    env_file: 
      - .env # Available inside container not in compose file
    environment:
      - DB_HOST=nestjs-auth-mysql
      - REDIS_HOST=nestjs-auth-redis
    depends_on:
      nestjs-auth-mysql:
        condition: service_healthy
      nestjs-auth-redis:
        condition: service_healthy
    command: npm run start:dev # Run in development mode

  nestjs-auth-mysql:
    container_name: nestjs-auth-mysql
    image: mysql:8.0
    restart: unless-stopped
    volumes:
      - mysql:/var/lib/mysql
    ports:
      - 3307:${DB_PORT}
    networks:
      - nestjs-auth-intranet
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      TZ: 'utc'
    command: --default-authentication-plugin=mysql_native_password
    healthcheck:
      test: ['CMD', 'mysqladmin', '-u${DB_USER}', '-p${DB_PASSWORD}', 'ping']
      interval: 5s
      retries: 3
      timeout: 3s

  nestjs-auth-redis:
    container_name: nestjs-auth-redis
    image: redis:alpine
    restart: unless-stopped
    volumes:
      - redis:/data
    ports:
      - 6380:${REDIS_PORT}
    networks:
      - nestjs-auth-intranet
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
      interval: 5s
      retries: 3
      timeout: 3s

volumes:
  mysql:
    name: nestjs-auth-mysql
  redis:
    name: nestjs-auth-redis

networks:
  nestjs-auth-intranet:
    name: nestjs-auth-intranet
    driver: bridge


================================================
FILE: nest-cli.json
================================================
{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "plugins": [
      {
        "name": "@nestjs/swagger",
        "options": {
          "dtoFileNameSuffix": [".entity.ts", ".dto.ts"],
          "controllerFileNameSuffix": [".controller.ts"],
          "classValidatorShim": true,
          "dtoKeyOfComment": "description",
          "controllerKeyOfComment": "description",
          "introspectComments": true
        }
      }
    ],
    "builder": "swc",
    "typeCheck": true
  }
}


================================================
FILE: package.json
================================================
{
  "name": "nestjs-authentication-and-authorization",
  "version": "0.0.1",
  "description": "NestJS authentication and authorization using Bcrypt, JWT & Redis",
  "author": "Anil Ahir",
  "private": false,
  "license": "MIT",
  "scripts": {
    "prebuild": "rimraf dist",
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test:watch": "jest --config ./test/unit/jest-unit.json --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "NODE_ENV=test jest --config ./test/e2e/jest-e2e.json --runInBand --detectOpenHandles",
    "test:unit": "NODE_ENV=test jest --config ./test/unit/jest-unit.json --runInBand",
    "prepare": "husky install",
    "docker:up": "docker-compose up -d -V --build",
    "docker:down": "docker-compose down"
  },
  "dependencies": {
    "@nestjs/common": "^11.1.19",
    "@nestjs/config": "^4.0.4",
    "@nestjs/core": "^11.1.19",
    "@nestjs/jwt": "^11.0.2",
    "@nestjs/mapped-types": "*",
    "@nestjs/platform-express": "^11.1.19",
    "@nestjs/swagger": "^11.4.1",
    "@nestjs/typeorm": "^11.0.1",
    "bcrypt": "^6.0.0",
    "class-transformer": "^0.5.1",
    "class-validator": "^0.15.1",
    "ioredis": "^5.10.1",
    "mysql2": "^3.22.2",
    "reflect-metadata": "^0.2.2",
    "rimraf": "^6.1.3",
    "rxjs": "^7.8.2",
    "swagger-ui-express": "^5.0.1",
    "typeorm": "^0.3.28"
  },
  "devDependencies": {
    "@golevelup/ts-jest": "^3.0.0",
    "@nestjs/cli": "^11.0.21",
    "@nestjs/schematics": "^11.1.0",
    "@nestjs/testing": "^11.1.19",
    "@swc/cli": "^0.8.1",
    "@swc/core": "^1.15.30",
    "@swc/jest": "^0.2.39",
    "@types/bcrypt": "^6.0.0",
    "@types/express": "^5.0.6",
    "@types/jest": "30.0.0",
    "@types/node": "^25.6.0",
    "@types/supertest": "^7.2.0",
    "@typescript-eslint/eslint-plugin": "^8.59.0",
    "@typescript-eslint/parser": "^8.59.0",
    "eslint": "^10.2.1",
    "eslint-config-prettier": "^10.1.8",
    "eslint-plugin-prettier": "^5.5.5",
    "husky": "^9.1.7",
    "jest": "^30.3.0",
    "lint-staged": "^16.4.0",
    "prettier": "^3.8.3",
    "source-map-support": "^0.5.21",
    "supertest": "^7.2.2",
    "ts-jest": "29.4.9",
    "ts-loader": "^9.5.7",
    "ts-node": "^10.9.2",
    "tsconfig-paths": "4.2.0",
    "typescript": "^6.0.3"
  },
  "engines": {
    "node": ">= 18"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s?$": [
        "@swc/jest"
      ]
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}


================================================
FILE: src/app.controller.ts
================================================
import { Controller, Get } from '@nestjs/common';
import { ApiOkResponse } from '@nestjs/swagger';

import { AppService } from './app.service';
import { Public } from './common/decorators/public.decorator';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @ApiOkResponse({ description: "Returns 'Hello World'" })
  @Public()
  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}


================================================
FILE: src/app.module.ts
================================================
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';

import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import appConfig from './common/config/app.config';
import databaseConfig from './common/config/database.config';
import jwtConfig from './common/config/jwt.config';
import { validate } from './common/validation/env.validation';
import { DatabaseModule } from './database/database.module';
import { UsersModule } from './users/users.module';
import { JwtAuthGuard } from './auth/guards/jwt-auth.guard';
import redisConfig from './common/config/redis.config';
import { RedisModule } from './redis/redis.module';
import swaggerConfig from './common/config/swagger.config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [appConfig, jwtConfig, databaseConfig, redisConfig, swaggerConfig],
      validate,
    }),
    DatabaseModule,
    RedisModule,
    AuthModule,
    UsersModule,
  ],
  controllers: [AppController],
  providers: [
    AppService,
    {
      provide: APP_GUARD,
      useClass: JwtAuthGuard,
    },
  ],
})
export class AppModule {}


================================================
FILE: src/app.service.ts
================================================
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}


================================================
FILE: src/auth/auth.controller.ts
================================================
import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
import {
  ApiBadRequestResponse,
  ApiBearerAuth,
  ApiConflictResponse,
  ApiCreatedResponse,
  ApiOkResponse,
  ApiTags,
  ApiUnauthorizedResponse,
} from '@nestjs/swagger';

import { ActiveUser } from '../common/decorators/active-user.decorator';
import { Public } from '../common/decorators/public.decorator';
import { AuthService } from './auth.service';
import { SignInDto } from './dto/sign-in.dto';
import { SignUpDto } from './dto/sign-up.dto';

@ApiTags('auth')
@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @ApiConflictResponse({
    description: 'User already exists',
  })
  @ApiBadRequestResponse({
    description: 'Return errors for invalid sign up fields',
  })
  @ApiCreatedResponse({
    description: 'User has been successfully signed up',
  })
  @Public()
  @Post('sign-up')
  signUp(@Body() signUpDto: SignUpDto): Promise<void> {
    return this.authService.signUp(signUpDto);
  }

  @ApiBadRequestResponse({
    description: 'Return errors for invalid sign in fields',
  })
  @ApiOkResponse({ description: 'User has been successfully signed in' })
  @HttpCode(HttpStatus.OK)
  @Public()
  @Post('sign-in')
  signIn(@Body() signInDto: SignInDto): Promise<{ accessToken: string }> {
    return this.authService.signIn(signInDto);
  }

  @ApiUnauthorizedResponse({ description: 'Unauthorized' })
  @ApiOkResponse({ description: 'User has been successfully signed out' })
  @ApiBearerAuth()
  @HttpCode(HttpStatus.OK)
  @Post('sign-out')
  signOut(@ActiveUser('id') userId: string): Promise<void> {
    return this.authService.signOut(userId);
  }
}


================================================
FILE: src/auth/auth.module.ts
================================================
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { TypeOrmModule } from '@nestjs/typeorm';

import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { BcryptService } from './bcrypt.service';
import { User } from '../users/entities/user.entity';
import jwtConfig from '../common/config/jwt.config';

@Module({
  imports: [
    TypeOrmModule.forFeature([User]),
    JwtModule.registerAsync(jwtConfig.asProvider()),
  ],
  controllers: [AuthController],
  providers: [AuthService, BcryptService],
  exports: [JwtModule],
})
export class AuthModule {}


================================================
FILE: src/auth/auth.service.ts
================================================
import {
  BadRequestException,
  ConflictException,
  Inject,
  Injectable,
} from '@nestjs/common';
import { ConfigType } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { randomUUID } from 'crypto';
import { Repository } from 'typeorm';

import jwtConfig from '../common/config/jwt.config';
import { MysqlErrorCode } from '../common/enums/error-codes.enum';
import { ActiveUserData } from '../common/interfaces/active-user-data.interface';
import { RedisService } from '../redis/redis.service';
import { User } from '../users/entities/user.entity';
import { BcryptService } from './bcrypt.service';
import { SignInDto } from './dto/sign-in.dto';
import { SignUpDto } from './dto/sign-up.dto';

@Injectable()
export class AuthService {
  constructor(
    @Inject(jwtConfig.KEY)
    private readonly jwtConfiguration: ConfigType<typeof jwtConfig>,
    private readonly bcryptService: BcryptService,
    private readonly jwtService: JwtService,
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
    private readonly redisService: RedisService,
  ) {}

  async signUp(signUpDto: SignUpDto): Promise<void> {
    const { email, password } = signUpDto;

    try {
      const user = new User();
      user.email = email;
      user.password = await this.bcryptService.hash(password);
      await this.userRepository.save(user);
    } catch (error) {
      if (error.code === MysqlErrorCode.UniqueViolation) {
        throw new ConflictException(`User [${email}] already exist`);
      }
      throw error;
    }
  }

  async signIn(signInDto: SignInDto): Promise<{ accessToken: string }> {
    const { email, password } = signInDto;

    const user = await this.userRepository.findOne({
      where: {
        email,
      },
    });
    if (!user) {
      throw new BadRequestException('Invalid email');
    }

    const isPasswordMatch = await this.bcryptService.compare(
      password,
      user.password,
    );
    if (!isPasswordMatch) {
      throw new BadRequestException('Invalid password');
    }

    return await this.generateAccessToken(user);
  }

  async signOut(userId: string): Promise<void> {
    this.redisService.delete(`user-${userId}`);
  }

  async generateAccessToken(
    user: Partial<User>,
  ): Promise<{ accessToken: string }> {
    const tokenId = randomUUID();

    await this.redisService.insert(`user-${user.id}`, tokenId);

    const accessToken = await this.jwtService.signAsync(
      {
        id: user.id,
        email: user.email,
        tokenId,
      } as ActiveUserData,
      {
        secret: this.jwtConfiguration.secret,
        expiresIn: this.jwtConfiguration.accessTokenTtl,
      },
    );

    return { accessToken };
  }
}


================================================
FILE: src/auth/bcrypt.service.ts
================================================
import { Injectable } from '@nestjs/common';
import { compare, genSalt, hash } from 'bcrypt';

@Injectable()
export class BcryptService {
  async hash(data: string): Promise<string> {
    const salt = await genSalt();
    return hash(data, salt);
  }

  async compare(data: string, encrypted: string): Promise<boolean> {
    return compare(data, encrypted);
  }
}


================================================
FILE: src/auth/dto/sign-in.dto.ts
================================================
import { ApiProperty } from '@nestjs/swagger';
import {
  IsEmail,
  IsNotEmpty,
  Matches,
  MaxLength,
  MinLength,
} from 'class-validator';

export class SignInDto {
  @ApiProperty({
    description: 'Email of user',
    example: 'atest@email.com',
  })
  @IsEmail()
  @MaxLength(255)
  @IsNotEmpty()
  readonly email: string;

  @ApiProperty({
    description: 'Password of user',
    example: 'Pass#123',
  })
  @MinLength(8, {
    message: 'password too short',
  })
  @MaxLength(20, {
    message: 'password too long',
  })
  @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
    message: 'password too weak',
  })
  @IsNotEmpty()
  readonly password: string;
}


================================================
FILE: src/auth/dto/sign-up.dto.ts
================================================
import { ApiProperty } from '@nestjs/swagger';
import {
  IsEmail,
  IsNotEmpty,
  Matches,
  MaxLength,
  MinLength,
} from 'class-validator';

import { Match } from '../../common/decorators/match.decorator';

export class SignUpDto {
  @ApiProperty({
    example: 'atest@email.com',
    description: 'Email of user',
  })
  @IsEmail()
  @MaxLength(255)
  @IsNotEmpty()
  readonly email: string;

  @ApiProperty({
    description: 'Password of user',
    example: 'Pass#123',
  })
  @MinLength(8, {
    message: 'password too short',
  })
  @MaxLength(20, {
    message: 'password too long',
  })
  @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
    message: 'password too weak',
  })
  @IsNotEmpty()
  readonly password: string;

  @ApiProperty({
    description: 'Repeat same value as in password field',
    example: 'Pass#123',
  })
  @Match('password')
  @IsNotEmpty()
  readonly passwordConfirm: string;
}


================================================
FILE: src/auth/guards/jwt-auth.guard.ts
================================================
import {
  CanActivate,
  ExecutionContext,
  Inject,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { ConfigType } from '@nestjs/config';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';

import jwtConfig from '../../common/config/jwt.config';
import { REQUEST_USER_KEY } from '../../common/constants';
import { ActiveUserData } from '../../common/interfaces/active-user-data.interface';
import { RedisService } from '../../redis/redis.service';

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(
    @Inject(jwtConfig.KEY)
    private readonly jwtConfiguration: ConfigType<typeof jwtConfig>,
    private readonly jwtService: JwtService,
    private readonly redisService: RedisService,
    private reflector: Reflector,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isPublic) {
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const token = this.getToken(request);
    if (!token) {
      throw new UnauthorizedException('Authorization token is required');
    }

    try {
      const payload = await this.jwtService.verifyAsync<ActiveUserData>(
        token,
        this.jwtConfiguration,
      );

      const isValidToken = await this.redisService.validate(
        `user-${payload.id}`,
        payload.tokenId,
      );
      if (!isValidToken) {
        throw new UnauthorizedException('Authorization token is not valid');
      }

      request[REQUEST_USER_KEY] = payload;
    } catch (error) {
      throw new UnauthorizedException(error.message);
    }

    return true;
  }

  private getToken(request: Request) {
    const [_, token] = request.headers.authorization?.split(' ') ?? [];
    return token;
  }
}


================================================
FILE: src/common/config/app.config.ts
================================================
import { registerAs } from '@nestjs/config';

export default registerAs('app', () => {
  return {
    nodeEnv: process.env.NODE_ENV || 'development',
    port: parseInt(process.env.PORT, 10) || 3000,
  };
});


================================================
FILE: src/common/config/database.config.ts
================================================
import { registerAs } from '@nestjs/config';

export default registerAs('database', () => {
  return {
    type: 'mysql',
    host: process.env.DB_HOST || 'localhost',
    port: parseInt(process.env.DB_PORT, 10) || 3306,
    username: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    name: process.env.DB_NAME,
  };
});


================================================
FILE: src/common/config/jwt.config.ts
================================================
import { registerAs } from '@nestjs/config';

export default registerAs('jwt', () => {
  return {
    secret: process.env.JWT_SECRET,
    accessTokenTtl: process.env.JWT_ACCESS_TOKEN_TTL,
  };
});


================================================
FILE: src/common/config/redis.config.ts
================================================
import { registerAs } from '@nestjs/config';

export default registerAs('redis', () => {
  return {
    host: process.env.REDIS_HOST,
    port: parseInt(process.env.REDIS_PORT, 10) || 6379,
    db: parseInt(process.env.REDIS_DATABASE, 10),
    keyPrefix: process.env.REDIS_KEY_PREFIX + ':',
    ...(process.env.REDIS_USERNAME && {
      username: process.env.REDIS_USERNAME,
    }),
    ...(process.env.REDIS_PASSWORD && {
      password: process.env.REDIS_PASSWORD,
    }),
  };
});


================================================
FILE: src/common/config/swagger.config.ts
================================================
import { registerAs } from '@nestjs/config';

export default registerAs('swagger', () => {
  return {
    siteTitle: process.env.SWAGGER_SITE_TITLE,
    docTitle: process.env.SWAGGER_DOC_TITLE,
    docDescription: process.env.SWAGGER_DOC_DESCRIPTION,
    docVersion: process.env.SWAGGER_DOC_VERSION,
  };
});


================================================
FILE: src/common/constants/index.ts
================================================
export const REQUEST_USER_KEY = 'user';


================================================
FILE: src/common/decorators/active-user.decorator.ts
================================================
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

import { REQUEST_USER_KEY } from '../constants';
import { ActiveUserData } from '../interfaces/active-user-data.interface';

export const ActiveUser = createParamDecorator(
  (field: keyof ActiveUserData | undefined, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user: ActiveUserData | undefined = request[REQUEST_USER_KEY];
    return field ? user?.[field] : user;
  },
);


================================================
FILE: src/common/decorators/match.decorator.ts
================================================
import {
  registerDecorator,
  ValidationArguments,
  ValidationOptions,
  ValidatorConstraint,
  ValidatorConstraintInterface,
} from 'class-validator';

export function Match(property: string, validationOptions?: ValidationOptions) {
  return (object: any, propertyName: string) => {
    registerDecorator({
      target: object.constructor,
      propertyName,
      options: validationOptions,
      constraints: [property],
      validator: MatchConstraint,
    });
  };
}

@ValidatorConstraint({ name: 'Match' })
export class MatchConstraint implements ValidatorConstraintInterface {
  validate(value: any, args: ValidationArguments) {
    const [relatedPropertyName] = args.constraints;
    const relatedValue = (args.object as any)[relatedPropertyName];
    return value === relatedValue;
  }
  defaultMessage(args: ValidationArguments) {
    return args.property + ' must match ' + args.constraints[0];
  }
}


================================================
FILE: src/common/decorators/public.decorator.ts
================================================
import { SetMetadata, CustomDecorator } from '@nestjs/common';

export const Public = (): CustomDecorator => SetMetadata('isPublic', true);


================================================
FILE: src/common/enums/environment.enum.ts
================================================
export enum Environment {
  Development = 'development',
  Production = 'production',
  Test = 'test',
}


================================================
FILE: src/common/enums/error-codes.enum.ts
================================================
export enum MysqlErrorCode {
  UniqueViolation = 'ER_DUP_ENTRY',
}


================================================
FILE: src/common/interceptors/transform.interceptor.ts
================================================
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
} from '@nestjs/common';
import { instanceToPlain } from 'class-transformer';
import { map, Observable } from 'rxjs';

@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(map((data) => instanceToPlain(data)));
  }
}


================================================
FILE: src/common/interfaces/active-user-data.interface.ts
================================================
export interface ActiveUserData {
  id: string;
  email: string;
  tokenId: string;
}


================================================
FILE: src/common/validation/env.validation.ts
================================================
import { plainToInstance } from 'class-transformer';
import {
  IsEnum,
  IsNotEmpty,
  IsNumber,
  IsOptional,
  IsString,
  validateSync,
} from 'class-validator';

import { Environment } from '../enums/environment.enum';

class EnvironmentVariables {
  @IsEnum(Environment)
  NODE_ENV: Environment;

  @IsNumber()
  @IsNotEmpty()
  PORT: number;

  @IsString()
  @IsNotEmpty()
  DB_HOST: string;

  @IsNumber()
  @IsNotEmpty()
  DB_PORT: number;

  @IsString()
  @IsNotEmpty()
  DB_USER: string;

  @IsString()
  @IsNotEmpty()
  DB_PASSWORD: string;

  @IsString()
  @IsNotEmpty()
  DB_NAME: string;

  @IsString()
  @IsNotEmpty()
  REDIS_HOST: string;

  @IsNumber()
  @IsNotEmpty()
  REDIS_PORT: number;

  @IsString()
  @IsOptional()
  REDIS_USERNAME: string;

  @IsString()
  @IsOptional()
  REDIS_PASSWORD: string;

  @IsNumber()
  @IsNotEmpty()
  REDIS_DATABASE: number;

  @IsString()
  @IsNotEmpty()
  REDIS_KEY_PREFIX: string;

  @IsString()
  @IsNotEmpty()
  JWT_SECRET: string;

  @IsNotEmpty()
  @IsNumber()
  JWT_ACCESS_TOKEN_TTL: number;

  @IsString()
  @IsNotEmpty()
  SWAGGER_SITE_TITLE: string;

  @IsString()
  @IsNotEmpty()
  SWAGGER_DOC_TITLE: string;

  @IsString()
  @IsNotEmpty()
  SWAGGER_DOC_DESCRIPTION: string;

  @IsString()
  @IsNotEmpty()
  SWAGGER_DOC_VERSION: string;
}

export function validate(config: Record<string, unknown>) {
  const validatedConfig = plainToInstance(EnvironmentVariables, config, {
    enableImplicitConversion: true,
  });
  const errors = validateSync(validatedConfig, {
    skipMissingProperties: false,
  });

  let errorMessage = errors
    .map((message) => message.constraints[Object.keys(message.constraints)[0]])
    .join('\n');

  const COLOR = {
    reset: '\x1b[0m',
    bright: '\x1b[1m',
    fgRed: '\x1b[31m',
  };

  errorMessage = `${COLOR.fgRed}${COLOR.bright}${errorMessage}${COLOR.reset}`;

  if (errors.length > 0) {
    throw new Error(errorMessage);
  }

  return validatedConfig;
}


================================================
FILE: src/database/database.module.ts
================================================
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        type: 'mysql',
        host: configService.get<string>('database.host'),
        port: configService.get<number>('database.port'),
        username: configService.get<string>('database.username'),
        password: configService.get<string>('database.password'),
        database: configService.get<string>('database.name'),
        autoLoadEntities: true,
        synchronize: true,
        logging: false,
      }),
    }),
  ],
})
export class DatabaseModule {}


================================================
FILE: src/main.ts
================================================
import { ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';

import { AppModule } from './app.module';
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
import { setupSwagger } from './swagger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  setupSwagger(app);

  app.useGlobalInterceptors(new TransformInterceptor());

  app.useGlobalPipes(
    new ValidationPipe({
      transform: true,
      whitelist: true,
      forbidUnknownValues: true,
      stopAtFirstError: true,
      validateCustomDecorators: true,
    }),
  );

  const configService = app.get(ConfigService);

  const port = configService.get('PORT');

  await app.listen(port, () => {
    console.log(`Application running at ${port}`);
  });
}

bootstrap();


================================================
FILE: src/metadata.ts
================================================
/* eslint-disable */
export default async () => {
  const t = {
    ['./users/entities/user.entity']: await import(
      './users/entities/user.entity'
    ),
  };
  return {
    '@nestjs/swagger': {
      models: [
        [
          import('./users/entities/user.entity'),
          {
            User: {
              id: { required: true, type: () => String },
              email: { required: true, type: () => String },
              createdAt: { required: true, type: () => Date },
              updatedAt: { required: true, type: () => Date },
            },
          },
        ],
        [
          import('./auth/dto/sign-in.dto'),
          {
            SignInDto: {
              email: { required: true, type: () => String, maxLength: 255 },
              password: {
                required: true,
                type: () => String,
                minLength: 8,
                maxLength: 20,
                pattern:
                  '/((?=.*\\d)|(?=.*\\W+))(?![.\\n])(?=.*[A-Z])(?=.*[a-z]).*$/',
              },
            },
          },
        ],
        [
          import('./auth/dto/sign-up.dto'),
          {
            SignUpDto: {
              email: { required: true, type: () => String, maxLength: 255 },
              password: {
                required: true,
                type: () => String,
                minLength: 8,
                maxLength: 20,
                pattern:
                  '/((?=.*\\d)|(?=.*\\W+))(?![.\\n])(?=.*[A-Z])(?=.*[a-z]).*$/',
              },
              passwordConfirm: { required: true, type: () => String },
            },
          },
        ],
      ],
      controllers: [
        [
          import('./app.controller'),
          { AppController: { getHello: { type: String } } },
        ],
        [
          import('./auth/auth.controller'),
          { AuthController: { signUp: {}, signIn: {}, signOut: {} } },
        ],
        [
          import('./users/users.controller'),
          {
            UsersController: {
              getMe: { type: t['./users/entities/user.entity'].User },
            },
          },
        ],
      ],
    },
  };
};


================================================
FILE: src/redis/redis.constants.ts
================================================
export const IORedisKey = 'IORedis';


================================================
FILE: src/redis/redis.module.ts
================================================
import { Global, Module, OnApplicationShutdown, Scope } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ModuleRef } from '@nestjs/core';
import { Redis } from 'ioredis';

import { IORedisKey } from './redis.constants';
import { RedisService } from './redis.service';

@Global()
@Module({
  imports: [ConfigModule],
  providers: [
    {
      provide: IORedisKey,
      useFactory: async (configService: ConfigService) => {
        return new Redis(configService.get('redis'));
      },
      inject: [ConfigService],
    },
    RedisService,
  ],
  exports: [RedisService],
})
export class RedisModule implements OnApplicationShutdown {
  constructor(private readonly moduleRef: ModuleRef) {}

  async onApplicationShutdown(signal?: string): Promise<void> {
    return new Promise<void>((resolve) => {
      const redis = this.moduleRef.get(IORedisKey);
      redis.quit();
      redis.on('end', () => {
        resolve();
      });
    });
  }
}


================================================
FILE: src/redis/redis.service.ts
================================================
import { Inject, Injectable } from '@nestjs/common';
import { Redis } from 'ioredis';

import { IORedisKey } from './redis.constants';

@Injectable()
export class RedisService {
  constructor(
    @Inject(IORedisKey)
    private readonly redisClient: Redis,
  ) {}

  async getKeys(pattern?: string): Promise<string[]> {
    return await this.redisClient.keys(pattern);
  }

  async insert(key: string, value: string | number): Promise<void> {
    await this.redisClient.set(key, value);
  }

  async get(key: string): Promise<string> {
    return this.redisClient.get(key);
  }

  async delete(key: string): Promise<void> {
    await this.redisClient.del(key);
  }

  async validate(key: string, value: string): Promise<boolean> {
    const storedValue = await this.redisClient.get(key);
    return storedValue === value;
  }
}


================================================
FILE: src/swagger.ts
================================================
import { INestApplication } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
  DocumentBuilder,
  SwaggerCustomOptions,
  SwaggerDocumentOptions,
  SwaggerModule,
} from '@nestjs/swagger';

import metadata from './metadata';

export const setupSwagger = async (app: INestApplication) => {
  const configService = app.get(ConfigService);
  const swaggerConfig = configService.get('swagger');

  const config = new DocumentBuilder()
    .setTitle(swaggerConfig.docTitle)
    .setDescription(swaggerConfig.docDescription)
    .setVersion(swaggerConfig.docVersion)
    .addBearerAuth()
    .build();

  const options: SwaggerDocumentOptions = {
    operationIdFactory: (controllerKey: string, methodKey: string) => methodKey,
  };

  await SwaggerModule.loadPluginMetadata(metadata);

  const document = SwaggerModule.createDocument(app, config, options);

  const customOptions: SwaggerCustomOptions = {
    swaggerOptions: {
      persistAuthorization: true,
      // defaultModelsExpandDepth: -1,
    },
    customSiteTitle: swaggerConfig.siteTitle,
  };

  SwaggerModule.setup('docs', app, document, customOptions);
};


================================================
FILE: src/users/entities/user.entity.ts
================================================
import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';
import { Exclude } from 'class-transformer';
import {
  Column,
  CreateDateColumn,
  Entity,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';

@Entity({
  name: 'users',
})
export class User {
  @ApiProperty({
    description: 'ID of user',
    example: '89c018cc-8a77-4dbd-94e1-dbaa710a2a9c',
  })
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @ApiProperty({ description: 'Email of user', example: 'atest@email.com' })
  @Column({ unique: true })
  email: string;

  @ApiHideProperty()
  @Column()
  @Exclude({ toPlainOnly: true })
  password: string;

  @ApiProperty({ description: 'Created date of user' })
  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;

  @ApiProperty({ description: 'Updated date of user' })
  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date;
}


================================================
FILE: src/users/users.controller.ts
================================================
import { Controller, Get } from '@nestjs/common';
import {
  ApiBearerAuth,
  ApiOkResponse,
  ApiTags,
  ApiUnauthorizedResponse,
} from '@nestjs/swagger';

import { ActiveUser } from '../common/decorators/active-user.decorator';
import { User } from './entities/user.entity';
import { UsersService } from './users.service';

@ApiTags('users')
@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @ApiUnauthorizedResponse({ description: 'Unauthorized' })
  @ApiOkResponse({ description: "Get logged in user's details", type: User })
  @ApiBearerAuth()
  @Get('me')
  async getMe(@ActiveUser('id') userId: string): Promise<User> {
    return this.usersService.getMe(userId);
  }
}


================================================
FILE: src/users/users.module.ts
================================================
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

import { User } from './entities/user.entity';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}


================================================
FILE: src/users/users.service.ts
================================================
import { BadRequestException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

import { User } from './entities/user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  async getMe(userId: string): Promise<User> {
    const user = await this.userRepository.findOne({
      where: {
        id: userId,
      },
    });
    if (!user) {
      throw new BadRequestException('User not found');
    }

    return user;
  }
}


================================================
FILE: test/e2e/app.e2e-spec.ts
================================================
import { HttpStatus } from '@nestjs/common';
import * as request from 'supertest';
import { DataSource } from 'typeorm';
import { Server } from 'http';

import { AppFactory } from '../factories/app.factory';
import { AuthService } from '../../src/auth/auth.service';
import { SignUpDto } from '../../src/auth/dto/sign-up.dto';
import { UserFactory } from '../factories/user.factory';
import { SignInDto } from '../../src/auth/dto/sign-in.dto';

describe('App (e2e)', () => {
  let app: AppFactory;
  let server: Server;
  let dataSource: DataSource;
  let authService: AuthService;

  beforeAll(async () => {
    app = await AppFactory.new();
    server = app.instance.getHttpServer();
    dataSource = app.dbSource;
    authService = app.instance.get(AuthService);
  });

  afterAll(async () => {
    await app.close();
  });

  beforeEach(async () => {
    await app.cleanupDB();
  });

  describe('AppModule', () => {
    describe('GET /', () => {
      it("should return 'Hello World'", () => {
        return request(app.instance.getHttpServer())
          .get('/')
          .expect(HttpStatus.OK)
          .expect('Hello World!');
      });
    });
  });

  describe('AuthModule', () => {
    describe('POST /auth/sign-up', () => {
      it('should create a new user', async () => {
        await new Promise((resolve) => setTimeout(resolve, 1));

        const signUpDto: SignUpDto = {
          email: 'atest@email.com',
          password: 'Pass#123',
          passwordConfirm: 'Pass#123',
        };

        return request(server)
          .post('/auth/sign-up')
          .send(signUpDto)
          .expect(HttpStatus.CREATED);
      });

      it('should return 400 for invalid sign up fields', async () => {
        await new Promise((resolve) => setTimeout(resolve, 1));

        const signUpDto: SignUpDto = {
          email: 'invalid-email',
          password: 'Pass#123',
          passwordConfirm: 'Pass#123',
        };

        return request(server)
          .post('/auth/sign-up')
          .send(signUpDto)
          .expect(HttpStatus.BAD_REQUEST);
      });

      it('should return 409 if user already exists', async () => {
        await UserFactory.new(dataSource).create({
          email: 'atest@email.com',
          password: 'Pass#123',
        });

        const signUpDto: SignUpDto = {
          email: 'atest@email.com',
          password: 'Pass#123',
          passwordConfirm: 'Pass#123',
        };

        return request(server)
          .post('/auth/sign-up')
          .send(signUpDto)
          .expect(HttpStatus.CONFLICT);
      });
    });

    describe('POST /auth/sign-in', () => {
      it('should sign in the user and return access token', async () => {
        const email = 'atest@email.com';
        const password = 'Pass#123';
        await UserFactory.new(dataSource).create({
          email,
          password,
        });

        const signInDto: SignInDto = {
          email,
          password,
        };

        return request(server)
          .post('/auth/sign-in')
          .send(signInDto)
          .expect(HttpStatus.OK)
          .expect((res) => {
            expect(res.body).toEqual({ accessToken: expect.any(String) });
          });
      });

      it('should return 400 for invalid sign in fields', async () => {
        const signInDto: SignInDto = {
          email: 'atest@email.com',
          password: '',
        };

        return request(server)
          .post('/auth/sign-in')
          .send(signInDto)
          .expect(HttpStatus.BAD_REQUEST);
      });
    });

    describe('POST /auth/sign-out', () => {
      it('should sign out the user', async () => {
        const user = await UserFactory.new(dataSource).create({
          email: 'atest@email.com',
          password: 'Pass#123',
        });

        const { accessToken } = await authService.generateAccessToken(user);

        return request(server)
          .post('/auth/sign-out')
          .set('Authorization', `Bearer ${accessToken}`)
          .expect(HttpStatus.OK);
      });

      it('should return 401 if not authorized', async () => {
        return request(server)
          .post('/auth/sign-out')
          .expect(HttpStatus.UNAUTHORIZED);
      });
    });
  });

  describe('UsersModule', () => {
    describe('GET /users/me', () => {
      it('should return 401 unauthorized when no access token provided', () => {
        return request(server).get('/users/me').expect(HttpStatus.UNAUTHORIZED);
      });

      it('should return user details when access token provided', async () => {
        const user = await UserFactory.new(dataSource).create({
          email: 'atest@email.com',
          password: 'Pass#123',
        });

        const { accessToken } = await authService.generateAccessToken(user);

        return request(server)
          .get('/users/me')
          .set('Authorization', `Bearer ${accessToken}`)
          .expect(HttpStatus.OK)
          .expect((res) => {
            expect(res.body).toEqual(
              expect.objectContaining({
                id: user.id,
                email: user.email,
              }),
            );
          });
      });
    });
  });
});


================================================
FILE: test/e2e/jest-e2e.json
================================================
{
  "moduleFileExtensions": ["js", "json", "ts"],
  "rootDir": "../../",
  "testEnvironment": "node",
  "testRegex": ".e2e-spec.ts$",
  "transform": {
    "^.+\\.(t|j)s$": "ts-jest"
  },
  "modulePaths": ["."]
}


================================================
FILE: test/factories/app.factory.ts
================================================
import { INestApplication, Type, ValidationPipe } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { DataSource } from 'typeorm';
import { Redis } from 'ioredis';
import { getDataSourceToken } from '@nestjs/typeorm';

import { AppModule } from 'src/app.module';
import { TransformInterceptor } from '../../src/common/interceptors/transform.interceptor';
import { IORedisKey } from '../../src/redis/redis.constants';

export class AppFactory {
  private constructor(
    private readonly appInstance: INestApplication,
    private readonly dataSource: DataSource,
    private readonly redis: Redis,
  ) {}

  get instance() {
    return this.appInstance;
  }

  get dbSource() {
    return this.dataSource;
  }

  static async new() {
    const module = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    const app = module.createNestApplication();

    app.useGlobalInterceptors(new TransformInterceptor());

    app.useGlobalPipes(
      new ValidationPipe({
        transform: true,
        whitelist: true,
        forbidUnknownValues: true,
        stopAtFirstError: true,
        validateCustomDecorators: true,
      }),
    );

    await app.init();

    const dataSource = module.get<DataSource>(
      getDataSourceToken() as Type<DataSource>,
    );

    const redis = module.get<Redis>(IORedisKey);

    return new AppFactory(app, dataSource, redis);
  }

  async close() {
    await this.appInstance.close();
  }

  async cleanupDB() {
    await this.redis.flushall();

    const tables = this.dataSource.manager.connection.entityMetadatas.map(
      (entity) => `${entity.tableName}`,
    );
    for (const table of tables) {
      await this.dataSource.manager.connection.query(`DELETE FROM ${table};`);
    }
  }
}


================================================
FILE: test/factories/user.factory.ts
================================================
import { DataSource, EntityManager } from 'typeorm';
import * as bcrypt from 'bcrypt';

import { User } from '../../src/users/entities/user.entity';

export class UserFactory {
  private dataSource: DataSource;

  static new(dataSource: DataSource) {
    const factory = new UserFactory();
    factory.dataSource = dataSource;
    return factory;
  }

  async create(user: Partial<User> = {}) {
    const userRepository = this.dataSource.getRepository(User);
    const salt = await bcrypt.genSalt();
    const password = await this.hashPassword(user.password, salt);
    const payload = {
      ...user,
      password,
    };
    return userRepository.save(payload);
  }

  private hashPassword(password: string, salt: string) {
    return bcrypt.hash(password, salt);
  }
}


================================================
FILE: test/unit/app.service.unit-spec.ts
================================================
import { Test, TestingModule } from '@nestjs/testing';

import { AppService } from '../../src/app.service';

describe('AppService', () => {
  let service: AppService;

  beforeEach(async () => {
    const moduleRef: TestingModule = await Test.createTestingModule({
      providers: [AppService],
    }).compile();

    service = moduleRef.get<AppService>(AppService);
  });

  describe('getHello', () => {
    it('should return "Hello World!"', () => {
      const result = service.getHello();

      expect(result).toEqual('Hello World!');
    });
  });
});


================================================
FILE: test/unit/auth/auth.service.unit-spec.ts
================================================
import { Test } from '@nestjs/testing';
import { ConfigType } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { Repository } from 'typeorm';
import { BadRequestException, ConflictException } from '@nestjs/common';
import { createMock } from '@golevelup/ts-jest';
import { getRepositoryToken } from '@nestjs/typeorm';

import { AuthService } from '../../../src/auth/auth.service';
import { BcryptService } from '../../../src/auth/bcrypt.service';
import { RedisService } from '../../../src/redis/redis.service';
import jwtConfig from '../../../src/common/config/jwt.config';
import { User } from '../../../src/users/entities/user.entity';
import { SignUpDto } from '../../../src/auth/dto/sign-up.dto';
import { MysqlErrorCode } from '../../../src/common/enums/error-codes.enum';
import { ActiveUserData } from '../../../src/common/interfaces/active-user-data.interface';

describe('AuthService', () => {
  let authService: AuthService;
  let bcryptService: BcryptService;
  let jwtService: JwtService;
  let userRepository: Repository<User>;
  let redisService: RedisService;
  let jwtConfiguration: ConfigType<typeof jwtConfig>;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [
        AuthService,
        { provide: BcryptService, useValue: createMock<BcryptService>() },
        { provide: JwtService, useValue: createMock<JwtService>() },
        { provide: RedisService, useValue: createMock<RedisService>() },
        {
          provide: getRepositoryToken(User),
          useClass: Repository,
        },
        {
          provide: jwtConfig.KEY,
          useValue: jwtConfig.asProvider(),
        },
      ],
    }).compile();

    authService = moduleRef.get<AuthService>(AuthService);
    bcryptService = moduleRef.get<BcryptService>(BcryptService);
    jwtService = moduleRef.get<JwtService>(JwtService);
    userRepository = moduleRef.get<Repository<User>>(getRepositoryToken(User));
    redisService = moduleRef.get<RedisService>(RedisService);
    jwtConfiguration = moduleRef.get<ConfigType<typeof jwtConfig>>(
      jwtConfig.KEY,
    );
  });

  describe('signUp', () => {
    const signUpDto: SignUpDto = {
      email: 'test@example.com',
      password: 'password',
      passwordConfirm: 'password',
    };
    let user: User;

    beforeEach(() => {
      user = new User();
      user.email = signUpDto.email;
      user.password = 'hashed_password';
    });

    it('should create a new user', async () => {
      const saveSpy = jest
        .spyOn(userRepository, 'save')
        .mockResolvedValueOnce(user);
      const hashSpy = jest
        .spyOn(bcryptService, 'hash')
        .mockResolvedValueOnce('hashed_password');

      await authService.signUp(signUpDto);

      expect(hashSpy).toHaveBeenCalledWith(signUpDto.password);
      expect(saveSpy).toHaveBeenCalledWith(user);
    });

    it('should throw a ConflictException if a user with the same email already exists', async () => {
      const saveSpy = jest
        .spyOn(userRepository, 'save')
        .mockRejectedValueOnce({ code: MysqlErrorCode.UniqueViolation });

      await expect(authService.signUp(signUpDto)).rejects.toThrowError(
        new ConflictException(`User [${signUpDto.email}] already exist`),
      );

      expect(saveSpy).toHaveBeenCalledWith(user);
    });

    it('should rethrow any other error that occurs during signup', async () => {
      const saveSpy = jest
        .spyOn(userRepository, 'save')
        .mockRejectedValueOnce(new Error('Unexpected error'));

      await expect(authService.signUp(signUpDto)).rejects.toThrowError(
        new Error('Unexpected error'),
      );

      expect(saveSpy).toHaveBeenCalledWith(user);
    });
  });

  describe('signIn', () => {
    it('should sign in a user and return an access token', async () => {
      const signInDto = {
        email: 'johndoe@example.com',
        password: 'password',
      };

      const user = new User();
      user.id = '123';
      user.email = signInDto.email;
      user.password = 'encryptedPassword';

      const encryptedPassword = 'encryptedPassword';
      const comparedPassword = true;
      const tokenId = expect.any(String);

      jest.spyOn(userRepository, 'findOne').mockResolvedValue(user);
      jest.spyOn(bcryptService, 'compare').mockResolvedValue(comparedPassword);
      jest
        .spyOn(authService, 'generateAccessToken')
        .mockResolvedValue({ accessToken: 'accessToken' });

      const result = await authService.signIn(signInDto);

      expect(result).toEqual({ accessToken: 'accessToken' });
      expect(userRepository.findOne).toHaveBeenCalledWith({
        where: { email: signInDto.email },
      });
      expect(bcryptService.compare).toHaveBeenCalledWith(
        signInDto.password,
        encryptedPassword,
      );
    });

    it('should throw an error when email is invalid', async () => {
      const signInDto = {
        email: 'invalid-email',
        password: 'Pass#123',
      };
      jest.spyOn(userRepository, 'findOne').mockResolvedValue(undefined);

      await expect(authService.signIn(signInDto)).rejects.toThrow(
        BadRequestException,
      );

      expect(userRepository.findOne).toHaveBeenCalledWith({
        where: { email: signInDto.email },
      });
    });

    it('should throw an error when password is invalid', async () => {
      const signInDto = {
        email: 'johndoe@example.com',
        password: 'password',
      };

      const user = new User();
      user.id = '123';
      user.email = signInDto.email;
      user.password = 'encryptedPassword';

      jest.spyOn(userRepository, 'findOne').mockResolvedValue(user);
      jest.spyOn(bcryptService, 'compare').mockResolvedValue(false);

      await expect(authService.signIn(signInDto)).rejects.toThrow(
        BadRequestException,
      );

      expect(userRepository.findOne).toHaveBeenCalledWith({
        where: { email: signInDto.email },
      });
      expect(bcryptService.compare).toHaveBeenCalledWith(
        signInDto.password,
        user.password,
      );
    });
  });

  describe('signOut', () => {
    it('should delete user token from Redis', async () => {
      const userId = 'test-user-id';
      const deleteSpy = jest
        .spyOn(redisService, 'delete')
        .mockResolvedValue(undefined);

      await authService.signOut(userId);

      expect(deleteSpy).toHaveBeenCalledWith(`user-${userId}`);
    });
  });

  describe('generateAccessToken', () => {
    it('should insert a token into Redis and return an access token', async () => {
      const user = { id: '123', email: 'test@example.com' };
      const tokenId = expect.any(String);
      const accessToken = 'test-access-token';
      (redisService.insert as any).mockResolvedValueOnce(undefined);
      (jwtService.signAsync as any).mockResolvedValueOnce(accessToken);

      const result = await authService.generateAccessToken(user);

      expect(redisService.insert).toHaveBeenCalledWith(
        `user-${user.id}`,
        tokenId,
      );
      expect(jwtService.signAsync).toHaveBeenCalledWith(
        { id: user.id, email: user.email, tokenId } as ActiveUserData,
        {
          secret: jwtConfiguration.secret,
          expiresIn: jwtConfiguration.accessTokenTtl,
        },
      );
      expect(result).toEqual({ accessToken });
    });
  });
});


================================================
FILE: test/unit/auth/bcrypt.service.unit-spec.ts
================================================
import { BcryptService } from '../../../src/auth/bcrypt.service';

describe('BcryptService', () => {
  let bcryptService: BcryptService;

  beforeEach(() => {
    bcryptService = new BcryptService();
  });

  describe('hash', () => {
    it('should return a hashed string', async () => {
      const data = 'password';

      const result = await bcryptService.hash(data);

      expect(result).not.toBe(data);
      expect(result).toBeDefined();
      expect(result).not.toBeNull();
      expect(typeof result).toBe('string');
    });
  });

  describe('compare', () => {
    it('should return true if the data matches the encrypted string', async () => {
      const data = 'password';
      const encrypted =
        '$2b$10$iUp/PtR8IlnyKFD5ZjP0X.DUg4.zFec3jr/XoMm9/rIXC0dzaRUmS';

      const result = await bcryptService.compare(data, encrypted);

      expect(result).toBe(true);
    });

    it('should return false if the data does not match the encrypted string', async () => {
      const data = 'password';
      const encrypted =
        '$2b$10$iUp/PtR8IlnyKFD5ZjP0X.DUg4.zFec3jr/XoMm9/rIXC0dzaRUmS';

      const result = await bcryptService.compare('wrong-password', encrypted);

      expect(result).toBe(false);
    });
  });
});


================================================
FILE: test/unit/auth/guards/jwt-auth.guard.unit-spec.ts
================================================
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { JwtService } from '@nestjs/jwt';
import { Reflector } from '@nestjs/core';
import { createMock } from '@golevelup/ts-jest';
import { ConfigModule } from '@nestjs/config';

import { JwtAuthGuard } from '../../../../src/auth/guards/jwt-auth.guard';
import { RedisService } from '../../../../src/redis/redis.service';
import jwtConfig from '../../../../src/common/config/jwt.config';

describe('JwtAuthGuard', () => {
  let guard: JwtAuthGuard;
  let redisService: RedisService;
  let jwtService: JwtService;
  let reflector: Reflector;
  let mockExecutionContext: ExecutionContext;

  beforeEach(async () => {
    const moduleRef: TestingModule = await Test.createTestingModule({
      imports: [
        ConfigModule.forRoot({
          load: [jwtConfig],
        }),
      ],
      providers: [
        JwtAuthGuard,
        {
          provide: RedisService,
          useValue: createMock<RedisService>(),
        },
        {
          provide: JwtService,
          useValue: createMock<JwtService>(),
        },
        {
          provide: Reflector,
          useValue: createMock<Reflector>(),
        },
      ],
    }).compile();

    guard = moduleRef.get<JwtAuthGuard>(JwtAuthGuard);
    redisService = moduleRef.get<RedisService>(RedisService);
    jwtService = moduleRef.get<JwtService>(JwtService);
    reflector = moduleRef.get<Reflector>(Reflector);
    mockExecutionContext = createMock<ExecutionContext>();
  });

  afterEach(() => {
    jest.resetAllMocks();
  });

  it('should be defined', () => {
    expect(guard).toBeDefined();
  });

  it('should allow access to public routes', async () => {
    jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(true);

    const result = await guard.canActivate(mockExecutionContext);

    expect(result).toBe(true);
  });

  it('should not allow access without a token', async () => {
    jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false);
    jest.spyOn(guard as any, 'getToken').mockReturnValue(undefined);

    await expect(guard.canActivate(mockExecutionContext)).rejects.toThrowError(
      new UnauthorizedException('Authorization token is required'),
    );
  });

  it('should not allow access with an invalid token', async () => {
    jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false);
    jest.spyOn(guard as any, 'getToken').mockReturnValue('invalid-token');
    jest.spyOn(redisService, 'validate').mockResolvedValue(false);

    await expect(guard.canActivate(mockExecutionContext)).rejects.toThrowError(
      new UnauthorizedException('Authorization token is not valid'),
    );
  });

  it('should allow access with a valid token', async () => {
    const validToken = 'valid-token';
    jest.spyOn(guard as any, 'getToken').mockReturnValue(validToken);
    jest.spyOn(redisService, 'validate').mockResolvedValue(true);

    const result = await guard.canActivate(mockExecutionContext);

    expect(result).toBe(true);
  });
});


================================================
FILE: test/unit/jest-unit.json
================================================
{
  "moduleFileExtensions": ["js", "json", "ts"],
  "rootDir": "../../",
  "testEnvironment": "node",
  "testRegex": ".unit-spec.ts$",
  "transform": {
    "^.+\\.(t|j)s$": "ts-jest"
  },
  "modulePaths": ["."]
}


================================================
FILE: test/unit/redis/redis.service.unit-spec.ts
================================================
import { createMock } from '@golevelup/ts-jest';
import { Test } from '@nestjs/testing';
import { Redis } from 'ioredis';

import { IORedisKey } from '../../../src/redis/redis.constants';
import { RedisService } from '../../../src/redis/redis.service';

describe('RedisService', () => {
  let redisService: RedisService;
  let redisClient: Redis;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [
        RedisService,
        {
          provide: IORedisKey,
          useValue: createMock<Redis>(),
        },
      ],
    }).compile();

    redisService = moduleRef.get<RedisService>(RedisService);
    redisClient = moduleRef.get<Redis>(IORedisKey);
  });

  afterEach(() => {
    jest.resetAllMocks();
  });

  it('should call redisClient.keys with the provided pattern when getKeys is called', async () => {
    const pattern = 'test*';
    const expectedKeys = ['test1', 'test2'];
    (redisClient.keys as any).mockResolvedValue(expectedKeys);

    const result = await redisService.getKeys(pattern);

    expect(redisClient.keys).toHaveBeenCalledWith(pattern);
    expect(result).toEqual(expectedKeys);
  });

  it('should call redisClient.set with the provided key and value when insert is called', async () => {
    const key = 'test-key';
    const value = 'test-value';

    await redisService.insert(key, value);

    expect(redisClient.set).toHaveBeenCalledWith(key, value);
  });

  it('should call redisClient.get with the provided key when get is called', async () => {
    const key = 'test-key';
    const expectedValue = 'test-value';
    (redisClient.get as any).mockResolvedValue(expectedValue);

    const result = await redisService.get(key);

    expect(redisClient.get).toHaveBeenCalledWith(key);
    expect(result).toEqual(expectedValue);
  });

  it('should call redisClient.del with the provided key when delete is called', async () => {
    const key = 'test-key';

    await redisService.delete(key);

    expect(redisClient.del).toHaveBeenCalledWith(key);
  });

  it('should return true if the stored value matches the provided value when validate is called', async () => {
    const key = 'test-key';
    const value = 'test-value';
    (redisClient.get as any).mockResolvedValue(value);

    const result = await redisService.validate(key, value);

    expect(redisClient.get).toHaveBeenCalledWith(key);
    expect(result).toBe(true);
  });

  it('should return false if the stored value does not match the provided value when validate is called', async () => {
    const key = 'test-key';
    const value = 'test-value';
    (redisClient.get as any).mockResolvedValue('other-value');

    const result = await redisService.validate(key, value);

    expect(redisClient.get).toHaveBeenCalledWith(key);
    expect(result).toBe(false);
  });
});


================================================
FILE: test/unit/users/users.service.unit-spec.ts
================================================
import { BadRequestException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

import { User } from '../../../src/users/entities/user.entity';
import { UsersService } from '../../../src/users/users.service';

describe('UsersService', () => {
  let usersService: UsersService;
  let userRepository: Repository<User>;

  beforeEach(async () => {
    const moduleRef: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        {
          provide: getRepositoryToken(User),
          useClass: Repository,
        },
      ],
    }).compile();

    usersService = moduleRef.get<UsersService>(UsersService);
    userRepository = moduleRef.get<Repository<User>>(getRepositoryToken(User));
  });

  describe('getMe', () => {
    it('should return a user with the specified ID', async () => {
      const userId = '123';
      const user = new User();
      user.id = userId;
      jest.spyOn(userRepository, 'findOne').mockResolvedValue(user);

      const result = await usersService.getMe(userId);

      expect(result).toEqual(user);
    });

    it('should throw a BadRequestException if user is not found', async () => {
      const userId = '123';
      jest.spyOn(userRepository, 'findOne').mockResolvedValue(undefined);

      await expect(usersService.getMe(userId)).rejects.toThrow(
        BadRequestException,
      );
    });
  });
});


================================================
FILE: tsconfig.build.json
================================================
{
  "extends": "./tsconfig.json",
  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false
  }
}
Download .txt
gitextract_hglp_yas/

├── .dockerignore
├── .eslintrc.js
├── .github/
│   ├── actions/
│   │   └── setvars/
│   │       └── action.yml
│   ├── dependabot.yml
│   ├── variables/
│   │   └── myvars.env
│   └── workflows/
│       ├── ci.yml
│       └── dependabot-auto-merge.yml
├── .gitignore
├── .husky/
│   ├── pre-commit
│   └── pre-push
├── .lintstagedrc.json
├── .prettierignore
├── .prettierrc
├── .swcrc
├── Dockerfile
├── LICENSE
├── README.md
├── docker-compose-test.yml
├── docker-compose.yml
├── nest-cli.json
├── package.json
├── src/
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   ├── auth/
│   │   ├── auth.controller.ts
│   │   ├── auth.module.ts
│   │   ├── auth.service.ts
│   │   ├── bcrypt.service.ts
│   │   ├── dto/
│   │   │   ├── sign-in.dto.ts
│   │   │   └── sign-up.dto.ts
│   │   └── guards/
│   │       └── jwt-auth.guard.ts
│   ├── common/
│   │   ├── config/
│   │   │   ├── app.config.ts
│   │   │   ├── database.config.ts
│   │   │   ├── jwt.config.ts
│   │   │   ├── redis.config.ts
│   │   │   └── swagger.config.ts
│   │   ├── constants/
│   │   │   └── index.ts
│   │   ├── decorators/
│   │   │   ├── active-user.decorator.ts
│   │   │   ├── match.decorator.ts
│   │   │   └── public.decorator.ts
│   │   ├── enums/
│   │   │   ├── environment.enum.ts
│   │   │   └── error-codes.enum.ts
│   │   ├── interceptors/
│   │   │   └── transform.interceptor.ts
│   │   ├── interfaces/
│   │   │   └── active-user-data.interface.ts
│   │   └── validation/
│   │       └── env.validation.ts
│   ├── database/
│   │   └── database.module.ts
│   ├── main.ts
│   ├── metadata.ts
│   ├── redis/
│   │   ├── redis.constants.ts
│   │   ├── redis.module.ts
│   │   └── redis.service.ts
│   ├── swagger.ts
│   └── users/
│       ├── entities/
│       │   └── user.entity.ts
│       ├── users.controller.ts
│       ├── users.module.ts
│       └── users.service.ts
├── test/
│   ├── e2e/
│   │   ├── app.e2e-spec.ts
│   │   └── jest-e2e.json
│   ├── factories/
│   │   ├── app.factory.ts
│   │   └── user.factory.ts
│   └── unit/
│       ├── app.service.unit-spec.ts
│       ├── auth/
│       │   ├── auth.service.unit-spec.ts
│       │   ├── bcrypt.service.unit-spec.ts
│       │   └── guards/
│       │       └── jwt-auth.guard.unit-spec.ts
│       ├── jest-unit.json
│       ├── redis/
│       │   └── redis.service.unit-spec.ts
│       └── users/
│           └── users.service.unit-spec.ts
├── tsconfig.build.json
└── tsconfig.json
Download .txt
SYMBOL INDEX (70 symbols across 27 files)

FILE: src/app.controller.ts
  class AppController (line 8) | class AppController {
    method constructor (line 9) | constructor(private readonly appService: AppService) {}
    method getHello (line 14) | getHello(): string {

FILE: src/app.module.ts
  class AppModule (line 40) | class AppModule {}

FILE: src/app.service.ts
  class AppService (line 4) | class AppService {
    method getHello (line 5) | getHello(): string {

FILE: src/auth/auth.controller.ts
  class AuthController (line 20) | class AuthController {
    method constructor (line 21) | constructor(private readonly authService: AuthService) {}
    method signUp (line 34) | signUp(@Body() signUpDto: SignUpDto): Promise<void> {
    method signIn (line 45) | signIn(@Body() signInDto: SignInDto): Promise<{ accessToken: string }> {
    method signOut (line 54) | signOut(@ActiveUser('id') userId: string): Promise<void> {

FILE: src/auth/auth.module.ts
  class AuthModule (line 20) | class AuthModule {}

FILE: src/auth/auth.service.ts
  class AuthService (line 23) | class AuthService {
    method constructor (line 24) | constructor(
    method signUp (line 34) | async signUp(signUpDto: SignUpDto): Promise<void> {
    method signIn (line 50) | async signIn(signInDto: SignInDto): Promise<{ accessToken: string }> {
    method signOut (line 73) | async signOut(userId: string): Promise<void> {
    method generateAccessToken (line 77) | async generateAccessToken(

FILE: src/auth/bcrypt.service.ts
  class BcryptService (line 5) | class BcryptService {
    method hash (line 6) | async hash(data: string): Promise<string> {
    method compare (line 11) | async compare(data: string, encrypted: string): Promise<boolean> {

FILE: src/auth/dto/sign-in.dto.ts
  class SignInDto (line 10) | class SignInDto {

FILE: src/auth/dto/sign-up.dto.ts
  class SignUpDto (line 12) | class SignUpDto {

FILE: src/auth/guards/jwt-auth.guard.ts
  class JwtAuthGuard (line 19) | class JwtAuthGuard implements CanActivate {
    method constructor (line 20) | constructor(
    method canActivate (line 28) | async canActivate(context: ExecutionContext): Promise<boolean> {
    method getToken (line 65) | private getToken(request: Request) {

FILE: src/common/constants/index.ts
  constant REQUEST_USER_KEY (line 1) | const REQUEST_USER_KEY = 'user';

FILE: src/common/decorators/match.decorator.ts
  function Match (line 9) | function Match(property: string, validationOptions?: ValidationOptions) {
  class MatchConstraint (line 22) | class MatchConstraint implements ValidatorConstraintInterface {
    method validate (line 23) | validate(value: any, args: ValidationArguments) {
    method defaultMessage (line 28) | defaultMessage(args: ValidationArguments) {

FILE: src/common/enums/environment.enum.ts
  type Environment (line 1) | enum Environment {

FILE: src/common/enums/error-codes.enum.ts
  type MysqlErrorCode (line 1) | enum MysqlErrorCode {

FILE: src/common/interceptors/transform.interceptor.ts
  class TransformInterceptor (line 11) | class TransformInterceptor implements NestInterceptor {
    method intercept (line 12) | intercept(context: ExecutionContext, next: CallHandler): Observable<an...

FILE: src/common/interfaces/active-user-data.interface.ts
  type ActiveUserData (line 1) | interface ActiveUserData {

FILE: src/common/validation/env.validation.ts
  class EnvironmentVariables (line 13) | class EnvironmentVariables {
  function validate (line 90) | function validate(config: Record<string, unknown>) {

FILE: src/database/database.module.ts
  class DatabaseModule (line 24) | class DatabaseModule {}

FILE: src/main.ts
  function bootstrap (line 9) | async function bootstrap() {

FILE: src/redis/redis.module.ts
  class RedisModule (line 24) | class RedisModule implements OnApplicationShutdown {
    method constructor (line 25) | constructor(private readonly moduleRef: ModuleRef) {}
    method onApplicationShutdown (line 27) | async onApplicationShutdown(signal?: string): Promise<void> {

FILE: src/redis/redis.service.ts
  class RedisService (line 7) | class RedisService {
    method constructor (line 8) | constructor(
    method getKeys (line 13) | async getKeys(pattern?: string): Promise<string[]> {
    method insert (line 17) | async insert(key: string, value: string | number): Promise<void> {
    method get (line 21) | async get(key: string): Promise<string> {
    method delete (line 25) | async delete(key: string): Promise<void> {
    method validate (line 29) | async validate(key: string, value: string): Promise<boolean> {

FILE: src/users/entities/user.entity.ts
  class User (line 14) | class User {

FILE: src/users/users.controller.ts
  class UsersController (line 15) | class UsersController {
    method constructor (line 16) | constructor(private readonly usersService: UsersService) {}
    method getMe (line 22) | async getMe(@ActiveUser('id') userId: string): Promise<User> {

FILE: src/users/users.module.ts
  class UsersModule (line 13) | class UsersModule {}

FILE: src/users/users.service.ts
  class UsersService (line 8) | class UsersService {
    method constructor (line 9) | constructor(
    method getMe (line 14) | async getMe(userId: string): Promise<User> {

FILE: test/factories/app.factory.ts
  class AppFactory (line 11) | class AppFactory {
    method constructor (line 12) | private constructor(
    method instance (line 18) | get instance() {
    method dbSource (line 22) | get dbSource() {
    method new (line 26) | static async new() {
    method close (line 56) | async close() {
    method cleanupDB (line 60) | async cleanupDB() {

FILE: test/factories/user.factory.ts
  class UserFactory (line 6) | class UserFactory {
    method new (line 9) | static new(dataSource: DataSource) {
    method create (line 15) | async create(user: Partial<User> = {}) {
    method hashPassword (line 26) | private hashPassword(password: string, salt: string) {
Condensed preview — 69 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (76K chars).
[
  {
    "path": ".dockerignore",
    "chars": 217,
    "preview": "# Versioning and metadata\n.vscode\n.git\n.gitignore\n.dockerignore\n\n# Build dependencies\ndist\nnode_modules\ncoverage\n\n# Envi"
  },
  {
    "path": ".eslintrc.js",
    "chars": 711,
    "preview": "module.exports = {\n  parser: '@typescript-eslint/parser',\n  parserOptions: {\n    project: 'tsconfig.json',\n    tsconfigR"
  },
  {
    "path": ".github/actions/setvars/action.yml",
    "chars": 455,
    "preview": "name: 'Set environment variables'\ndescription: 'Configures environment variables for a workflow'\ninputs:\n  varFilePath:\n"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 269,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: npm\n    directory: '/'\n    schedule:\n      interval: 'daily'\n    open-pull-re"
  },
  {
    "path": ".github/variables/myvars.env",
    "chars": 449,
    "preview": "NODE_ENV=test\nPORT=3000\n\nDB_HOST=localhost\nDB_PORT=3306\nDB_USER=admin\nDB_PASSWORD=test123!\nDB_NAME=nestjs-auth\n\nREDIS_HO"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1816,
    "preview": "name: CI Testing\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    types: [opened, synchronize, reopened]\n\njobs:\n  u"
  },
  {
    "path": ".github/workflows/dependabot-auto-merge.yml",
    "chars": 919,
    "preview": "name: Dependabot auto-merge PRs if CI Testing succeeds\n\non:\n  pull_request_target:\n  workflow_run:\n    workflows: ['CI T"
  },
  {
    "path": ".gitignore",
    "chars": 412,
    "preview": "# 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*\n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 69,
    "preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": ".husky/pre-push",
    "chars": 91,
    "preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpm run test:unit && npm run test:e2e\n"
  },
  {
    "path": ".lintstagedrc.json",
    "chars": 181,
    "preview": "{\n  \"**/*.{js,jsx,ts,tsx}\": [\n    \"eslint --fix\",\n    \"prettier --config ./.prettierrc --write\"\n  ],\n  \"**/*.{css,scss,m"
  },
  {
    "path": ".prettierignore",
    "chars": 20,
    "preview": "dist/\nnode_modules/\n"
  },
  {
    "path": ".prettierrc",
    "chars": 51,
    "preview": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\"\n}"
  },
  {
    "path": ".swcrc",
    "chars": 426,
    "preview": "{\n  \"$schema\": \"https://json.schemastore.org/swcrc\",\n  \"sourceMaps\": true,\n  \"jsc\": {\n    \"parser\": {\n      \"syntax\": \"t"
  },
  {
    "path": "Dockerfile",
    "chars": 2010,
    "preview": "###################\n# BUILD FOR LOCAL DEVELOPMENT\n###################\n\nFROM node:18-alpine As development\n\n# Create app "
  },
  {
    "path": "LICENSE",
    "chars": 1066,
    "preview": "MIT License\n\nCopyright (c) 2023 Anil Ahir\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
  },
  {
    "path": "README.md",
    "chars": 2058,
    "preview": "# NestJS Authentication\n\n![Workflow Test](https://github.com/anilahir/nestjs-authentication-and-authorization/actions/wo"
  },
  {
    "path": "docker-compose-test.yml",
    "chars": 363,
    "preview": "services:\n  mysql:\n    image: mysql:8.0\n    ports:\n      - 3306:3306\n    environment:\n      MYSQL_RANDOM_ROOT_PASSWORD: "
  },
  {
    "path": "docker-compose.yml",
    "chars": 1858,
    "preview": "services:\n  nestjs-auth-api:\n    container_name: nestjs-auth-api\n    image: nestjs-auth-api\n    restart: unless-stopped\n"
  },
  {
    "path": "nest-cli.json",
    "chars": 582,
    "preview": "{\n  \"$schema\": \"https://json.schemastore.org/nest-cli\",\n  \"collection\": \"@nestjs/schematics\",\n  \"sourceRoot\": \"src\",\n  \""
  },
  {
    "path": "package.json",
    "chars": 3016,
    "preview": "{\n  \"name\": \"nestjs-authentication-and-authorization\",\n  \"version\": \"0.0.1\",\n  \"description\": \"NestJS authentication and"
  },
  {
    "path": "src/app.controller.ts",
    "chars": 458,
    "preview": "import { Controller, Get } from '@nestjs/common';\nimport { ApiOkResponse } from '@nestjs/swagger';\n\nimport { AppService "
  },
  {
    "path": "src/app.module.ts",
    "chars": 1263,
    "preview": "import { Module } from '@nestjs/common';\nimport { ConfigModule } from '@nestjs/config';\nimport { APP_GUARD } from '@nest"
  },
  {
    "path": "src/app.service.ts",
    "chars": 142,
    "preview": "import { Injectable } from '@nestjs/common';\n\n@Injectable()\nexport class AppService {\n  getHello(): string {\n    return "
  },
  {
    "path": "src/auth/auth.controller.ts",
    "chars": 1716,
    "preview": "import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';\nimport {\n  ApiBadRequestResponse,\n  ApiBe"
  },
  {
    "path": "src/auth/auth.module.ts",
    "chars": 637,
    "preview": "import { Module } from '@nestjs/common';\nimport { JwtModule } from '@nestjs/jwt';\nimport { TypeOrmModule } from '@nestjs"
  },
  {
    "path": "src/auth/auth.service.ts",
    "chars": 2784,
    "preview": "import {\n  BadRequestException,\n  ConflictException,\n  Inject,\n  Injectable,\n} from '@nestjs/common';\nimport { ConfigTyp"
  },
  {
    "path": "src/auth/bcrypt.service.ts",
    "chars": 364,
    "preview": "import { Injectable } from '@nestjs/common';\nimport { compare, genSalt, hash } from 'bcrypt';\n\n@Injectable()\nexport clas"
  },
  {
    "path": "src/auth/dto/sign-in.dto.ts",
    "chars": 689,
    "preview": "import { ApiProperty } from '@nestjs/swagger';\nimport {\n  IsEmail,\n  IsNotEmpty,\n  Matches,\n  MaxLength,\n  MinLength,\n} "
  },
  {
    "path": "src/auth/dto/sign-up.dto.ts",
    "chars": 935,
    "preview": "import { ApiProperty } from '@nestjs/swagger';\nimport {\n  IsEmail,\n  IsNotEmpty,\n  Matches,\n  MaxLength,\n  MinLength,\n} "
  },
  {
    "path": "src/auth/guards/jwt-auth.guard.ts",
    "chars": 1964,
    "preview": "import {\n  CanActivate,\n  ExecutionContext,\n  Inject,\n  Injectable,\n  UnauthorizedException,\n} from '@nestjs/common';\nim"
  },
  {
    "path": "src/common/config/app.config.ts",
    "chars": 209,
    "preview": "import { registerAs } from '@nestjs/config';\n\nexport default registerAs('app', () => {\n  return {\n    nodeEnv: process.e"
  },
  {
    "path": "src/common/config/database.config.ts",
    "chars": 335,
    "preview": "import { registerAs } from '@nestjs/config';\n\nexport default registerAs('database', () => {\n  return {\n    type: 'mysql'"
  },
  {
    "path": "src/common/config/jwt.config.ts",
    "chars": 197,
    "preview": "import { registerAs } from '@nestjs/config';\n\nexport default registerAs('jwt', () => {\n  return {\n    secret: process.en"
  },
  {
    "path": "src/common/config/redis.config.ts",
    "chars": 484,
    "preview": "import { registerAs } from '@nestjs/config';\n\nexport default registerAs('redis', () => {\n  return {\n    host: process.en"
  },
  {
    "path": "src/common/config/swagger.config.ts",
    "chars": 309,
    "preview": "import { registerAs } from '@nestjs/config';\n\nexport default registerAs('swagger', () => {\n  return {\n    siteTitle: pro"
  },
  {
    "path": "src/common/constants/index.ts",
    "chars": 40,
    "preview": "export const REQUEST_USER_KEY = 'user';\n"
  },
  {
    "path": "src/common/decorators/active-user.decorator.ts",
    "chars": 493,
    "preview": "import { createParamDecorator, ExecutionContext } from '@nestjs/common';\n\nimport { REQUEST_USER_KEY } from '../constants"
  },
  {
    "path": "src/common/decorators/match.decorator.ts",
    "chars": 919,
    "preview": "import {\n  registerDecorator,\n  ValidationArguments,\n  ValidationOptions,\n  ValidatorConstraint,\n  ValidatorConstraintIn"
  },
  {
    "path": "src/common/decorators/public.decorator.ts",
    "chars": 140,
    "preview": "import { SetMetadata, CustomDecorator } from '@nestjs/common';\n\nexport const Public = (): CustomDecorator => SetMetadata"
  },
  {
    "path": "src/common/enums/environment.enum.ts",
    "chars": 105,
    "preview": "export enum Environment {\n  Development = 'development',\n  Production = 'production',\n  Test = 'test',\n}\n"
  },
  {
    "path": "src/common/enums/error-codes.enum.ts",
    "chars": 67,
    "preview": "export enum MysqlErrorCode {\n  UniqueViolation = 'ER_DUP_ENTRY',\n}\n"
  },
  {
    "path": "src/common/interceptors/transform.interceptor.ts",
    "chars": 425,
    "preview": "import {\n  CallHandler,\n  ExecutionContext,\n  Injectable,\n  NestInterceptor,\n} from '@nestjs/common';\nimport { instanceT"
  },
  {
    "path": "src/common/interfaces/active-user-data.interface.ts",
    "chars": 86,
    "preview": "export interface ActiveUserData {\n  id: string;\n  email: string;\n  tokenId: string;\n}\n"
  },
  {
    "path": "src/common/validation/env.validation.ts",
    "chars": 1966,
    "preview": "import { plainToInstance } from 'class-transformer';\nimport {\n  IsEnum,\n  IsNotEmpty,\n  IsNumber,\n  IsOptional,\n  IsStri"
  },
  {
    "path": "src/database/database.module.ts",
    "chars": 805,
    "preview": "import { Module } from '@nestjs/common';\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport { TypeOrmM"
  },
  {
    "path": "src/main.ts",
    "chars": 878,
    "preview": "import { ValidationPipe } from '@nestjs/common';\nimport { ConfigService } from '@nestjs/config';\nimport { NestFactory } "
  },
  {
    "path": "src/metadata.ts",
    "chars": 2153,
    "preview": "/* eslint-disable */\nexport default async () => {\n  const t = {\n    ['./users/entities/user.entity']: await import(\n    "
  },
  {
    "path": "src/redis/redis.constants.ts",
    "chars": 37,
    "preview": "export const IORedisKey = 'IORedis';\n"
  },
  {
    "path": "src/redis/redis.module.ts",
    "chars": 993,
    "preview": "import { Global, Module, OnApplicationShutdown, Scope } from '@nestjs/common';\nimport { ConfigModule, ConfigService } fr"
  },
  {
    "path": "src/redis/redis.service.ts",
    "chars": 829,
    "preview": "import { Inject, Injectable } from '@nestjs/common';\nimport { Redis } from 'ioredis';\n\nimport { IORedisKey } from './red"
  },
  {
    "path": "src/swagger.ts",
    "chars": 1150,
    "preview": "import { INestApplication } from '@nestjs/common';\nimport { ConfigService } from '@nestjs/config';\nimport {\n  DocumentBu"
  },
  {
    "path": "src/users/entities/user.entity.ts",
    "chars": 880,
    "preview": "import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';\nimport { Exclude } from 'class-transformer';\nimport {\n  "
  },
  {
    "path": "src/users/users.controller.ts",
    "chars": 745,
    "preview": "import { Controller, Get } from '@nestjs/common';\nimport {\n  ApiBearerAuth,\n  ApiOkResponse,\n  ApiTags,\n  ApiUnauthorize"
  },
  {
    "path": "src/users/users.module.ts",
    "chars": 392,
    "preview": "import { Module } from '@nestjs/common';\nimport { TypeOrmModule } from '@nestjs/typeorm';\n\nimport { User } from './entit"
  },
  {
    "path": "src/users/users.service.ts",
    "chars": 607,
    "preview": "import { BadRequestException, Injectable } from '@nestjs/common';\nimport { InjectRepository } from '@nestjs/typeorm';\nim"
  },
  {
    "path": "test/e2e/app.e2e-spec.ts",
    "chars": 5196,
    "preview": "import { HttpStatus } from '@nestjs/common';\nimport * as request from 'supertest';\nimport { DataSource } from 'typeorm';"
  },
  {
    "path": "test/e2e/jest-e2e.json",
    "chars": 212,
    "preview": "{\n  \"moduleFileExtensions\": [\"js\", \"json\", \"ts\"],\n  \"rootDir\": \"../../\",\n  \"testEnvironment\": \"node\",\n  \"testRegex\": \".e"
  },
  {
    "path": "test/factories/app.factory.ts",
    "chars": 1784,
    "preview": "import { INestApplication, Type, ValidationPipe } from '@nestjs/common';\nimport { Test } from '@nestjs/testing';\nimport "
  },
  {
    "path": "test/factories/user.factory.ts",
    "chars": 776,
    "preview": "import { DataSource, EntityManager } from 'typeorm';\nimport * as bcrypt from 'bcrypt';\n\nimport { User } from '../../src/"
  },
  {
    "path": "test/unit/app.service.unit-spec.ts",
    "chars": 559,
    "preview": "import { Test, TestingModule } from '@nestjs/testing';\n\nimport { AppService } from '../../src/app.service';\n\ndescribe('A"
  },
  {
    "path": "test/unit/auth/auth.service.unit-spec.ts",
    "chars": 7405,
    "preview": "import { Test } from '@nestjs/testing';\nimport { ConfigType } from '@nestjs/config';\nimport { JwtService } from '@nestjs"
  },
  {
    "path": "test/unit/auth/bcrypt.service.unit-spec.ts",
    "chars": 1247,
    "preview": "import { BcryptService } from '../../../src/auth/bcrypt.service';\n\ndescribe('BcryptService', () => {\n  let bcryptService"
  },
  {
    "path": "test/unit/auth/guards/jwt-auth.guard.unit-spec.ts",
    "chars": 3087,
    "preview": "import { ExecutionContext, UnauthorizedException } from '@nestjs/common';\nimport { Test, TestingModule } from '@nestjs/t"
  },
  {
    "path": "test/unit/jest-unit.json",
    "chars": 213,
    "preview": "{\n  \"moduleFileExtensions\": [\"js\", \"json\", \"ts\"],\n  \"rootDir\": \"../../\",\n  \"testEnvironment\": \"node\",\n  \"testRegex\": \".u"
  },
  {
    "path": "test/unit/redis/redis.service.unit-spec.ts",
    "chars": 2831,
    "preview": "import { createMock } from '@golevelup/ts-jest';\nimport { Test } from '@nestjs/testing';\nimport { Redis } from 'ioredis'"
  },
  {
    "path": "test/unit/users/users.service.unit-spec.ts",
    "chars": 1505,
    "preview": "import { BadRequestException } from '@nestjs/common';\nimport { Test, TestingModule } from '@nestjs/testing';\nimport { ge"
  },
  {
    "path": "tsconfig.build.json",
    "chars": 97,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"exclude\": [\"node_modules\", \"test\", \"dist\", \"**/*spec.ts\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "chars": 546,
    "preview": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"emitDecorat"
  }
]

About this extraction

This page contains the full source code of the anilahir/nestjs-authentication-and-authorization GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 69 files (67.1 KB), approximately 18.7k tokens, and a symbol index with 70 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!