├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── nest-cli.json ├── package-lock.json ├── package.json ├── src ├── app.controller.spec.ts ├── app.controller.ts ├── app.module.ts ├── app.service.ts ├── auth │ ├── auth.controller.spec.ts │ ├── auth.controller.ts │ ├── auth.module.ts │ ├── auth.service.spec.ts │ ├── auth.service.ts │ ├── dto │ │ └── auth.dto.ts │ ├── jwtPayload.interface.ts │ ├── requestWithUser.interface.ts │ └── strategies │ │ ├── accessToken.strategy.ts │ │ └── refreshToken.strategy.ts ├── chats │ ├── chat.gateway.ts │ ├── chats.controller.spec.ts │ ├── chats.controller.ts │ ├── chats.module.ts │ ├── chats.service.spec.ts │ ├── chats.service.ts │ ├── dto │ │ └── message.dto.ts │ └── message.schema.ts ├── common │ └── guards │ │ ├── accessToken.guard.ts │ │ └── refreshToken.guard.ts ├── filters │ └── http-exception.filter.ts ├── main.ts ├── shared │ └── config │ │ └── mongodb.config.ts ├── users │ ├── decorators │ │ └── match.decorator.ts │ ├── dto │ │ ├── create-user.dto.ts │ │ └── update-user.dto.ts │ ├── schemas │ │ └── user.schema.ts │ ├── users.controller.spec.ts │ ├── users.controller.ts │ ├── users.module.ts │ ├── users.service.spec.ts │ └── users.service.ts └── utils │ ├── mongooseClassSerializer.interceptor.ts │ ├── override-typescript-rules │ └── eslint.ts │ └── swagger │ ├── constants.ts │ ├── index.ts │ └── option.type.ts ├── test ├── app.e2e-spec.ts └── jest-e2e.json ├── tsconfig.build.json └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | MONGO_URI= -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir : __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | .env 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 6 | [circleci-url]: https://circleci.com/gh/nestjs/nest 7 | 8 |

A progressive Node.js framework for building efficient and scalable server-side applications.

9 |

10 | NPM Version 11 | Package License 12 | NPM Downloads 13 | CircleCI 14 | Coverage 15 | Discord 16 | Backers on Open Collective 17 | Sponsors on Open Collective 18 | 19 | Support us 20 | 21 |

22 | 24 | 25 | ## Description 26 | 27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 28 | 29 | ## Installation 30 | 31 | ```bash 32 | $ npm install 33 | ``` 34 | 35 | ## Running the app 36 | 37 | ```bash 38 | # development 39 | $ npm run start 40 | 41 | # watch mode 42 | $ npm run start:dev 43 | 44 | # production mode 45 | $ npm run start:prod 46 | ``` 47 | 48 | ## Test 49 | 50 | ```bash 51 | # unit tests 52 | $ npm run test 53 | 54 | # e2e tests 55 | $ npm run test:e2e 56 | 57 | # test coverage 58 | $ npm run test:cov 59 | ``` 60 | 61 | ## Support 62 | 63 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 64 | 65 | ## Stay in touch 66 | 67 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 68 | - Website - [https://nestjs.com](https://nestjs.com/) 69 | - Twitter - [@nestframework](https://twitter.com/nestframework) 70 | 71 | ## License 72 | 73 | Nest is [MIT licensed](LICENSE). 74 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-app", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@nestjs/common": "^9.0.0", 25 | "@nestjs/config": "^2.3.1", 26 | "@nestjs/core": "^9.0.0", 27 | "@nestjs/jwt": "^10.0.2", 28 | "@nestjs/mongoose": "^9.2.1", 29 | "@nestjs/passport": "^9.0.3", 30 | "@nestjs/platform-express": "^9.0.0", 31 | "@nestjs/platform-socket.io": "^9.3.9", 32 | "@nestjs/swagger": "^6.2.1", 33 | "@nestjs/websockets": "^9.3.9", 34 | "@types/passport-jwt": "^3.0.8", 35 | "@types/socket.io": "^3.0.2", 36 | "argon2": "^0.30.3", 37 | "class-transformer": "^0.5.1", 38 | "class-validator": "^0.14.0", 39 | "cookie-parser": "^1.4.6", 40 | "helmet": "^6.0.1", 41 | "passport-jwt": "^4.0.1", 42 | "reflect-metadata": "^0.1.13", 43 | "rimraf": "^3.0.2", 44 | "rxjs": "^7.2.0" 45 | }, 46 | "devDependencies": { 47 | "@nestjs/cli": "^9.0.0", 48 | "@nestjs/schematics": "^9.0.0", 49 | "@nestjs/testing": "^9.0.0", 50 | "@types/express": "^4.17.13", 51 | "@types/jest": "28.1.8", 52 | "@types/node": "^16.0.0", 53 | "@types/supertest": "^2.0.11", 54 | "@typescript-eslint/eslint-plugin": "^5.0.0", 55 | "@typescript-eslint/parser": "^5.0.0", 56 | "eslint": "^8.0.1", 57 | "eslint-config-prettier": "^8.3.0", 58 | "eslint-plugin-prettier": "^4.0.0", 59 | "jest": "28.1.3", 60 | "prettier": "^2.3.2", 61 | "source-map-support": "^0.5.20", 62 | "supertest": "^6.1.3", 63 | "ts-jest": "28.0.8", 64 | "ts-loader": "^9.2.3", 65 | "ts-node": "^10.0.0", 66 | "tsconfig-paths": "4.1.0", 67 | "typescript": "^4.7.4" 68 | }, 69 | "jest": { 70 | "moduleFileExtensions": [ 71 | "js", 72 | "json", 73 | "ts" 74 | ], 75 | "rootDir": "src", 76 | "testRegex": ".*\\.spec\\.ts$", 77 | "transform": { 78 | "^.+\\.(t|j)s$": "ts-jest" 79 | }, 80 | "collectCoverageFrom": [ 81 | "**/*.(t|j)s" 82 | ], 83 | "coverageDirectory": "../coverage", 84 | "testEnvironment": "node" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication, Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | import { MongooseModule } from '@nestjs/mongoose'; 5 | import { ConfigModule, ConfigService } from '@nestjs/config'; 6 | import { ChatsModule } from './chats/chats.module'; 7 | import mongodbConfig from '././shared/config/mongodb.config'; 8 | 9 | 10 | @Module({ 11 | imports: [ 12 | ConfigModule.forRoot({ 13 | isGlobal: true, 14 | load: [mongodbConfig] 15 | }), 16 | MongooseModule.forRootAsync({ 17 | imports: [ConfigModule], 18 | useFactory: (configService: ConfigService) => ({ 19 | uri: configService.get('mongodb.uri') 20 | }), 21 | inject: [ConfigService] 22 | }), 23 | ChatsModule 24 | ], 25 | controllers: [AppController], 26 | providers: [AppService], 27 | }) 28 | export class AppModule { 29 | static port: number | string; 30 | 31 | constructor(private readonly configService: ConfigService) { 32 | AppModule.port = this.configService.get('PORT'); 33 | } 34 | 35 | static getBaseUrl(app: INestApplication): string { 36 | let baseUrl = app.getHttpServer().address().address; 37 | if (baseUrl == '0.0.0.0' || baseUrl == '::') { 38 | return (baseUrl = 'localhost'); 39 | } 40 | } 41 | } 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/auth/auth.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthController } from './auth.controller'; 3 | 4 | describe('AuthController', () => { 5 | let controller: AuthController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [AuthController], 10 | }).compile(); 11 | 12 | controller = module.get(AuthController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { ApiTags, ApiBody, ApiCreatedResponse } from '@nestjs/swagger'; 2 | import { AuthDto } from './dto/auth.dto'; 3 | import { CreateUserDto } from 'src/users/dto/create-user.dto'; 4 | import { AuthService } from './auth.service'; 5 | import { Body, Controller, Get, Post, Req, UseGuards } from '@nestjs/common'; 6 | import RequestWithUser from './requestWithUser.interface'; 7 | import { AccessTokenGuard } from '../common/guards/accessToken.guard' 8 | import { RefreshTokenGuard } from '../common/guards/refreshToken.guard' 9 | 10 | @Controller('auth') 11 | @ApiTags('Authentication') 12 | export class AuthController { 13 | constructor(private authService: AuthService) {} 14 | 15 | @ApiBody({ 16 | description: 'Contains properties to create User', 17 | type: CreateUserDto, 18 | 19 | }) 20 | @ApiCreatedResponse({ 21 | status: 201, 22 | description: 'returns 201 status and a refresh and access token when a user successfully signs up', 23 | }) 24 | @Post('signup') 25 | signup(@Body() createUserDto: CreateUserDto) { 26 | return this.authService.signUp(createUserDto); 27 | } 28 | 29 | 30 | @ApiBody({ 31 | description: 'Contains properties login a User', 32 | type: AuthDto, 33 | 34 | }) 35 | @ApiCreatedResponse({ 36 | status: 200, 37 | description: 'returns 200 status and a refresh and access token when a user successfully signs in', 38 | }) 39 | @Post('signin') 40 | signin(@Body() data: AuthDto) { 41 | return this.authService.signIn(data); 42 | } 43 | 44 | 45 | @ApiCreatedResponse({ 46 | status: 200, 47 | description: 'returns 200 status and logs a user out', 48 | }) 49 | @UseGuards(AccessTokenGuard) 50 | @Get('logout') 51 | logout(@Req() req: RequestWithUser) { 52 | this.authService.logout(req.user['sub']); 53 | } 54 | 55 | @ApiCreatedResponse({ 56 | status: 200, 57 | description: 'returns 200 status and return a refresh and access token', 58 | }) 59 | @UseGuards(RefreshTokenGuard) 60 | @Get('refresh') 61 | async refreshTokens(@Req() req: RequestWithUser) { 62 | const userId = req.user['sub']; 63 | const refreshToken = req.user['refreshToken']; 64 | const data = await this.authService.refreshTokens(userId, refreshToken) 65 | return { 66 | message: 'success', 67 | data: { 68 | ...data 69 | } 70 | } 71 | } 72 | 73 | 74 | @ApiCreatedResponse({ 75 | status: 200, 76 | description: 'returns 200 status current logged in User object', 77 | }) 78 | @UseGuards(AccessTokenGuard) 79 | @Get('/me') 80 | getCurrentUser(@Req() req: RequestWithUser) { 81 | const user = req.user 82 | return { 83 | message: 'success', 84 | data: { 85 | ...user 86 | } 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { AccessTokenStrategy } from './strategies/accessToken.strategy'; 2 | import { Module } from '@nestjs/common'; 3 | import { AuthController } from './auth.controller'; 4 | import { AuthService } from './auth.service'; 5 | import { JwtModule } from '@nestjs/jwt'; 6 | import { RefreshTokenStrategy } from './strategies/refreshToken.strategy'; 7 | import { UsersModule } from 'src/users/users.module'; 8 | 9 | @Module({ 10 | 11 | imports: [UsersModule, JwtModule.register({})], 12 | controllers: [AuthController], 13 | providers: [AuthService, AccessTokenStrategy, RefreshTokenStrategy] 14 | }) 15 | export class AuthModule {} 16 | -------------------------------------------------------------------------------- /src/auth/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthService } from './auth.service'; 3 | 4 | describe('AuthService', () => { 5 | let service: AuthService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [AuthService], 10 | }).compile(); 11 | 12 | service = module.get(AuthService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { AuthDto } from './dto/auth.dto'; 2 | import { CreateUserDto } from './../users/dto/create-user.dto'; 3 | import { ConfigService } from '@nestjs/config'; 4 | import { UsersService } from './../users/users.service'; 5 | import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common'; 6 | import * as argon2 from 'argon2' 7 | import { JwtService } from '@nestjs/jwt'; 8 | import JwtPayload from './jwtPayload.interface'; 9 | 10 | @Injectable() 11 | export class AuthService { 12 | constructor( 13 | private usersService: UsersService, 14 | private configService: ConfigService, 15 | private jwtService: JwtService 16 | ) {} 17 | 18 | async signUp(createUserDto: CreateUserDto): Promise { 19 | const user = await this.usersService.findByEmail(createUserDto.email) 20 | 21 | if(user) throw new BadRequestException('User already exists') 22 | 23 | // hash password 24 | const hash = await this.hashData(createUserDto.password) 25 | const newUser = await this.usersService.create({ 26 | ...createUserDto, 27 | password: hash 28 | }) 29 | 30 | const tokens = await this.getTokens(newUser._id, newUser.email) 31 | await this.updateRefreshToken(newUser._id, tokens.refreshToken) 32 | return { ...tokens, ...user } 33 | } 34 | 35 | 36 | async signIn(authDto: AuthDto) { 37 | const user = await this.usersService.findByEmail(authDto.email) 38 | 39 | if(!user) throw new NotFoundException('User does not exist') 40 | 41 | const passwordMatches = await argon2.verify(user.password, authDto.password) 42 | 43 | if (!passwordMatches) throw new BadRequestException('Incorrect email or password') 44 | 45 | const tokens = await this.getTokens(user._id, user.email) 46 | await this.updateRefreshToken(user._id, tokens.refreshToken) 47 | return { ...tokens, ...user } 48 | 49 | } 50 | 51 | async logout(userId: string) { 52 | return this.usersService.update(userId, { refreshToken: null }) 53 | } 54 | 55 | async hashData(data: string) { 56 | return argon2.hash(data) 57 | } 58 | 59 | async updateRefreshToken(userId: string, refreshToken: string) { 60 | const hashedRefreshToken = await this.hashData(refreshToken) 61 | 62 | await this.usersService.update(userId, { refreshToken: hashedRefreshToken }) 63 | } 64 | 65 | async getTokens(userId: string, email: string) { 66 | const [ accessToken, refreshToken ] = await Promise.all([ 67 | this.jwtService.signAsync( 68 | { 69 | sub: userId, 70 | email 71 | }, 72 | { 73 | secret: this.configService.get('JWT_ACCESS_SECRET'), 74 | expiresIn: '15m' 75 | } 76 | ), 77 | this.jwtService.signAsync( 78 | { 79 | sub: userId, 80 | email 81 | }, 82 | { 83 | secret: this.configService.get('JWT_REFRESH_SECRET'), 84 | expiresIn: '7d' 85 | } 86 | ) 87 | ]) 88 | 89 | return { 90 | accessToken, 91 | refreshToken 92 | } 93 | } 94 | 95 | 96 | async refreshTokens(userId: string, refreshToken: string) { 97 | const user = await this.usersService.findById(userId); 98 | if (!user || !user.refreshToken) 99 | throw new ForbiddenException('Access Denied'); 100 | const refreshTokenMatches = await argon2.verify( 101 | user.refreshToken, 102 | refreshToken, 103 | ); 104 | if (!refreshTokenMatches) throw new ForbiddenException('Access Denied'); 105 | const tokens = await this.getTokens(user._id, user.email); 106 | await this.updateRefreshToken(user._id, tokens.refreshToken); 107 | return { ...tokens, ...user }; 108 | 109 | } 110 | 111 | 112 | public async getUserFromAuthenticationToken(token: string) { 113 | const payload: JwtPayload = this.jwtService.verify(token, { 114 | secret: this.configService.get('JWT_ACCESS_TOKEN_SECRET'), 115 | }); 116 | 117 | const userId = payload.sub 118 | 119 | if (userId) { 120 | return this.usersService.findById(userId); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/auth/dto/auth.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger" 2 | import { IsNotEmpty, IsString } from "class-validator" 3 | 4 | export class AuthDto { 5 | @ApiProperty({ 6 | type: String, 7 | description: 'password property of a user', 8 | example: 'password', 9 | required: true 10 | }) 11 | @IsString() 12 | @IsNotEmpty() 13 | password: string 14 | 15 | @ApiProperty({ 16 | type: String, 17 | description: 'email property of a user and validates that it is unique', 18 | example: 'danny@example.com', 19 | required: true, 20 | }) 21 | @IsString() 22 | @IsNotEmpty() 23 | email: string 24 | } -------------------------------------------------------------------------------- /src/auth/jwtPayload.interface.ts: -------------------------------------------------------------------------------- 1 | export default interface JwtPayload { 2 | sub: string, 3 | email: string 4 | } -------------------------------------------------------------------------------- /src/auth/requestWithUser.interface.ts: -------------------------------------------------------------------------------- 1 | import { UserDocument } from './../users/schemas/user.schema'; 2 | import { Request } from "express"; 3 | 4 | export default interface RequestWithUser extends Request { 5 | user: UserDocument 6 | } -------------------------------------------------------------------------------- /src/auth/strategies/accessToken.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common"; 2 | import { PassportStrategy } from "@nestjs/passport"; 3 | import { ExtractJwt, Strategy } from "passport-jwt"; 4 | import JwtPayload from "../jwtPayload.interface"; 5 | 6 | @Injectable() 7 | export class AccessTokenStrategy extends PassportStrategy(Strategy, 'jwt') { 8 | constructor() { 9 | super({ 10 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 11 | secretOrKey: process.env.JWT_ACCESS_SECRET 12 | }) 13 | } 14 | 15 | 16 | validate(payload: JwtPayload) { 17 | return payload 18 | } 19 | } -------------------------------------------------------------------------------- /src/auth/strategies/refreshToken.strategy.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt } from 'passport-jwt'; 2 | import { Strategy } from 'passport-jwt'; 3 | import { PassportStrategy } from '@nestjs/passport'; 4 | import { Injectable } from '@nestjs/common'; 5 | import JwtPayload from '../jwtPayload.interface'; 6 | import { Request } from 'express'; 7 | 8 | 9 | @Injectable() 10 | export class RefreshTokenStrategy extends PassportStrategy(Strategy, 'jwt-refresh') { 11 | constructor() { 12 | super({ 13 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 14 | secretOrKey: process.env.JWT_REFRESH_SECRET, 15 | passReqToCallback: true 16 | }) 17 | } 18 | 19 | validate(req: Request, payload: JwtPayload) { 20 | const refreshToken = req.get('Authorization').replace('Bearer', '').trim() 21 | return { ...payload, refreshToken } 22 | } 23 | } -------------------------------------------------------------------------------- /src/chats/chat.gateway.ts: -------------------------------------------------------------------------------- 1 | import { ChatsService } from './chats.service'; 2 | import { 3 | MessageBody, 4 | SubscribeMessage, 5 | WebSocketGateway, 6 | WebSocketServer, 7 | OnGatewayConnection, 8 | ConnectedSocket 9 | } from '@nestjs/websockets'; 10 | import { Server, Socket } from 'socket.io'; 11 | import { MessageDto } from './dto/message.dto'; 12 | 13 | @WebSocketGateway() 14 | export class ChatGateway implements OnGatewayConnection { 15 | @WebSocketServer() 16 | server: Server; 17 | 18 | constructor(private chatsService: ChatsService) {} 19 | 20 | async handleConnection(socket: Socket) { 21 | await this.chatsService.getUserFromSocket(socket) 22 | } 23 | 24 | @SubscribeMessage('send_message') 25 | async listenForMessages(@MessageBody() message: MessageDto, @ConnectedSocket() socket: Socket) { 26 | 27 | const user = await this.chatsService.getUserFromSocket(socket) 28 | const newmessage = await this.chatsService.createMessage(message, user._id) 29 | 30 | this.server.sockets.emit('receive_message', newmessage); 31 | 32 | return newmessage 33 | } 34 | 35 | @SubscribeMessage('get_all_messages') 36 | async getAllMessages(@ConnectedSocket() socket: Socket) { 37 | 38 | await this.chatsService.getUserFromSocket(socket) 39 | const messages = await this.chatsService.getAllMessages() 40 | 41 | this.server.sockets.emit('receive_message', messages); 42 | 43 | return messages 44 | } 45 | 46 | 47 | } -------------------------------------------------------------------------------- /src/chats/chats.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ChatsController } from './chats.controller'; 3 | 4 | describe('ChatsController', () => { 5 | let controller: ChatsController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [ChatsController], 10 | }).compile(); 11 | 12 | controller = module.get(ChatsController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/chats/chats.controller.ts: -------------------------------------------------------------------------------- 1 | import RequestWithUser from 'src/auth/requestWithUser.interface'; 2 | import { AccessTokenGuard } from 'src/common/guards/accessToken.guard'; 3 | import { MessageDto } from './dto/message.dto'; 4 | import { ChatsService } from './chats.service'; 5 | import { Body, Controller, Post, Req, UseGuards, Get } from '@nestjs/common'; 6 | 7 | @Controller('chats') 8 | export class ChatsController { 9 | constructor(private chatsService: ChatsService) {} 10 | 11 | @UseGuards(AccessTokenGuard) 12 | @Post() 13 | async createMessage(@Body() message: MessageDto, @Req() req: RequestWithUser) { 14 | const userId = req.user['sub']; 15 | return this.chatsService.createMessage(message, userId) 16 | } 17 | 18 | 19 | @UseGuards(AccessTokenGuard) 20 | @Get() 21 | async getAllMessages() { 22 | return this.chatsService.getAllMessages() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/chats/chats.module.ts: -------------------------------------------------------------------------------- 1 | import { Message, MessageSchema } from './message.schema'; 2 | import { Module } from '@nestjs/common'; 3 | import { ChatsController } from './chats.controller'; 4 | import { ChatsService } from './chats.service'; 5 | import { MongooseModule } from '@nestjs/mongoose'; 6 | 7 | @Module({ 8 | imports: [MongooseModule.forFeature([ 9 | { name: Message.name, schema: MessageSchema } 10 | ])], 11 | controllers: [ChatsController], 12 | providers: [ChatsService] 13 | }) 14 | export class ChatsModule {} 15 | -------------------------------------------------------------------------------- /src/chats/chats.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ChatsService } from './chats.service'; 3 | 4 | describe('ChatsService', () => { 5 | let service: ChatsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ChatsService], 10 | }).compile(); 11 | 12 | service = module.get(ChatsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/chats/chats.service.ts: -------------------------------------------------------------------------------- 1 | import { Message, MessageDocument } from './message.schema'; 2 | import { Socket } from 'socket.io'; 3 | import { AuthService } from './../auth/auth.service'; 4 | import { Injectable } from '@nestjs/common'; 5 | import { WsException } from '@nestjs/websockets'; 6 | import { InjectModel } from '@nestjs/mongoose'; 7 | import { Model } from 'mongoose'; 8 | import { MessageDto } from './dto/message.dto'; 9 | 10 | 11 | @Injectable() 12 | export class ChatsService { 13 | constructor(private authService: AuthService, @InjectModel(Message.name) private messageModel: Model) {} 14 | 15 | async getUserFromSocket(socket: Socket) { 16 | let auth_token = socket.handshake.headers.authorization; 17 | // get the token itself without "Bearer" 18 | auth_token = auth_token.split(' ')[1]; 19 | 20 | const user = this.authService.getUserFromAuthenticationToken( 21 | auth_token 22 | ); 23 | 24 | if (!user) { 25 | throw new WsException('Invalid credentials.'); 26 | } 27 | 28 | return user; 29 | } 30 | 31 | 32 | async createMessage(message: MessageDto, userId: string) { 33 | const newMessage = new this.messageModel({...message, userId}) 34 | await newMessage.save 35 | return newMessage 36 | } 37 | 38 | async getAllMessages() { 39 | return this.messageModel.find().populate('user') 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/chats/dto/message.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | import { IsNotEmpty, IsString } from "class-validator"; 3 | 4 | export class MessageDto { 5 | @ApiProperty() 6 | @IsString() 7 | @IsNotEmpty() 8 | message: string 9 | 10 | } -------------------------------------------------------------------------------- /src/chats/message.schema.ts: -------------------------------------------------------------------------------- 1 | import { User } from './../users/schemas/user.schema'; 2 | import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; 3 | import mongoose, { Document } from "mongoose"; 4 | 5 | export type MessageDocument = Message & Document; 6 | 7 | 8 | 9 | 10 | @Schema({ 11 | toJSON: { 12 | getters: true, 13 | virtuals: true, 14 | }, 15 | timestamps: true, 16 | }) 17 | export class Message { 18 | @Prop({ required: true, unique: true }) 19 | message: string 20 | 21 | @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' }) 22 | user: User 23 | 24 | } 25 | 26 | 27 | const MessageSchema = SchemaFactory.createForClass(Message) 28 | 29 | 30 | 31 | export { MessageSchema }; 32 | -------------------------------------------------------------------------------- /src/common/guards/accessToken.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | 5 | @Injectable() 6 | export class AccessTokenGuard extends AuthGuard('jwt') {} -------------------------------------------------------------------------------- /src/common/guards/refreshToken.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | 5 | @Injectable() 6 | export class RefreshTokenGuard extends AuthGuard('jwt-refresh') {} -------------------------------------------------------------------------------- /src/filters/http-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ExceptionFilter, 3 | Catch, 4 | ArgumentsHost, 5 | HttpException, 6 | } from '@nestjs/common'; 7 | import { Response } from 'express'; 8 | 9 | @Catch(HttpException) 10 | export class HttpExceptionFilter implements ExceptionFilter { 11 | catch(exception: HttpException, host: ArgumentsHost) { 12 | const ctx = host.switchToHttp(); 13 | const response = ctx.getResponse(); 14 | const status = exception.getStatus(); 15 | 16 | const res: any = exception.getResponse(); 17 | if (!res.message) { 18 | response.status(status).json({ 19 | error: true, 20 | msg: res, 21 | }); 22 | } else if (!Array.isArray(res.message)) { 23 | response.status(status).json({ 24 | error: true, 25 | msg: res.message, 26 | }); 27 | } else { 28 | response.status(status).json({ 29 | error: true, 30 | msg: res.message[0], 31 | }); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import * as cookieParser from 'cookie-parser' 4 | import helmet from 'helmet' 5 | import { Logger, ValidationPipe } from '@nestjs/common'; 6 | import { setupSwagger } from './utils /swagger'; 7 | import { HttpExceptionFilter } from './filters/http-exception.filter'; 8 | 9 | 10 | 11 | 12 | async function bootstrap() { 13 | const app = await NestFactory.create(AppModule, { cors: true }); 14 | app.enableCors({ 15 | origin: '*', 16 | credentials: true 17 | }) 18 | app.use(cookieParser()) 19 | app.useGlobalPipes( 20 | new ValidationPipe({ 21 | whitelist: true 22 | }) 23 | ) 24 | const logger = new Logger('Main') 25 | 26 | app.setGlobalPrefix('api/v1') 27 | app.useGlobalFilters(new HttpExceptionFilter()); 28 | 29 | setupSwagger(app) 30 | app.use(helmet()) 31 | 32 | 33 | await app.listen(AppModule.port) 34 | 35 | // log docs 36 | const baseUrl = AppModule.getBaseUrl(app) 37 | const url = `http://${baseUrl}:${AppModule.port}` 38 | logger.log(`API Documentation available at ${url}/docs`); 39 | } 40 | bootstrap(); 41 | 42 | // node -e "console.log(require('crypto').randomBytes(256).toString('base64'));" 43 | -------------------------------------------------------------------------------- /src/shared/config/mongodb.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | 4 | /** 5 | * Mongo database connection config 6 | */ 7 | export default registerAs('mongodb', () => { 8 | const { 9 | MONGO_URI 10 | } = process.env; 11 | return { 12 | uri:`${MONGO_URI}`, 13 | }; 14 | }); 15 | -------------------------------------------------------------------------------- /src/users/decorators/match.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | registerDecorator, 3 | ValidationArguments, 4 | ValidationOptions, 5 | ValidatorConstraint, 6 | ValidatorConstraintInterface, 7 | } from 'class-validator'; 8 | 9 | export function Match(property: string, validationOptions?: ValidationOptions) { 10 | return (object: any, propertyName: string) => { 11 | registerDecorator({ 12 | target: object.constructor, 13 | propertyName, 14 | options: validationOptions, 15 | constraints: [property], 16 | validator: MatchConstraint, 17 | }); 18 | }; 19 | } 20 | 21 | @ValidatorConstraint({ name: 'Match' }) 22 | export class MatchConstraint implements ValidatorConstraintInterface { 23 | validate(value: any, args: ValidationArguments) { 24 | const [relatedPropertyName] = args.constraints; 25 | const relatedValue = (args.object as any)[relatedPropertyName]; 26 | return value === relatedValue; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/users/dto/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | import { IsNotEmpty, IsString } from "class-validator"; 3 | import { Match } from "../decorators/match.decorator"; 4 | 5 | export class CreateUserDto { 6 | @ApiProperty({ 7 | type: String, 8 | description: 'username property of a user and ensures it is unique', 9 | example: 'danny', 10 | required: true 11 | }) 12 | @IsString() 13 | @IsNotEmpty() 14 | username: string 15 | 16 | @ApiProperty({ 17 | type: String, 18 | description: 'password property of a user', 19 | example: 'password', 20 | required: true 21 | }) 22 | @IsString() 23 | @IsNotEmpty() 24 | password: string 25 | 26 | @ApiProperty({ 27 | type: String, 28 | description: 'allows user to confirm the previously entered password and validates them', 29 | example: 'password', 30 | required: true 31 | }) 32 | @IsString() 33 | @IsNotEmpty() 34 | @Match('password', { message: 'Passwords should match'}) 35 | passwordConfirm: string 36 | 37 | @ApiProperty({ 38 | type: String, 39 | description: 'email property of a user and validates that it is unique', 40 | example: 'danny@example.com', 41 | required: true, 42 | }) 43 | @IsString() 44 | @IsNotEmpty() 45 | email: string 46 | 47 | 48 | refreshToken: string; 49 | 50 | } -------------------------------------------------------------------------------- /src/users/dto/update-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from "@nestjs/swagger"; 2 | import { CreateUserDto } from "./create-user.dto"; 3 | 4 | export class UpdateUserDto extends PartialType(CreateUserDto) {} -------------------------------------------------------------------------------- /src/users/schemas/user.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; 2 | import mongoose, { Document } from "mongoose"; 3 | 4 | export type UserDocument = User & Document; 5 | 6 | 7 | 8 | 9 | @Schema({ 10 | toJSON: { 11 | getters: true, 12 | virtuals: true, 13 | }, 14 | timestamps: true, 15 | }) 16 | export class User { 17 | @Prop({ required: true, unique: true }) 18 | username: string 19 | 20 | @Prop({ required: true }) 21 | password: string 22 | 23 | @Prop({ required: true, unique: true }) 24 | email: string 25 | 26 | @Prop() 27 | refreshToken: string 28 | 29 | } 30 | 31 | 32 | const UserSchema = SchemaFactory.createForClass(User) 33 | 34 | 35 | 36 | export { UserSchema }; 37 | -------------------------------------------------------------------------------- /src/users/users.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UsersController } from './users.controller'; 3 | 4 | describe('UsersController', () => { 5 | let controller: UsersController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [UsersController], 10 | }).compile(); 11 | 12 | controller = module.get(UsersController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@nestjs/common'; 2 | 3 | @Controller('users') 4 | 5 | export class UsersController { 6 | 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { MongooseModule } from '@nestjs/mongoose'; 2 | import { Module } from '@nestjs/common'; 3 | import { UsersController } from './users.controller'; 4 | import { UsersService } from './users.service'; 5 | import { User, UserSchema } from './schemas/user.schema'; 6 | 7 | @Module({ 8 | imports: [MongooseModule.forFeature([ 9 | { name: User.name, schema: UserSchema } 10 | ])], 11 | controllers: [UsersController], 12 | providers: [UsersService], 13 | exports: [UsersService] 14 | }) 15 | export class UsersModule {} 16 | -------------------------------------------------------------------------------- /src/users/users.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UsersService } from './users.service'; 3 | 4 | describe('UsersService', () => { 5 | let service: UsersService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [UsersService], 10 | }).compile(); 11 | 12 | service = module.get(UsersService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { UpdateUserDto } from './dto/update-user.dto'; 2 | import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; 3 | import { InjectModel } from '@nestjs/mongoose'; 4 | import { User, UserDocument } from './schemas/user.schema'; 5 | import { Model } from 'mongoose'; 6 | import { CreateUserDto } from './dto/create-user.dto'; 7 | 8 | 9 | @Injectable() 10 | export class UsersService { 11 | constructor(@InjectModel(User.name) private userModel: Model) {} 12 | 13 | async create(createUserDto: CreateUserDto): Promise { 14 | const createdUser = new this.userModel(createUserDto) 15 | return createdUser.save() 16 | } 17 | 18 | 19 | async findAll(): Promise { 20 | return this.userModel.find().exec() 21 | } 22 | 23 | 24 | async findById(id: string): Promise { 25 | const user = await this.userModel.findById(id).exec() 26 | 27 | if (!user) throw new HttpException('User not found', HttpStatus.NOT_FOUND) 28 | 29 | return user 30 | } 31 | 32 | async findByUsername(username: string): Promise { 33 | return this.userModel.findOne({ username }) 34 | 35 | } 36 | 37 | async findByEmail(email: string): Promise { 38 | return this.userModel.findOne({ email }) 39 | } 40 | 41 | 42 | async update(id: string, updateUserDto: UpdateUserDto): Promise { 43 | const user = await this.userModel.findByIdAndUpdate(id, updateUserDto, { new: true }).exec() 44 | 45 | if (!user) throw new HttpException('User not found', HttpStatus.NOT_FOUND) 46 | 47 | return user 48 | } 49 | 50 | async remove(id: string): Promise { 51 | const user = await this.userModel.findByIdAndDelete(id).exec() 52 | 53 | if (!user) throw new HttpException('User not found', HttpStatus.NOT_FOUND) 54 | 55 | return user 56 | } 57 | 58 | 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/utils /mongooseClassSerializer.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClassSerializerInterceptor, 3 | PlainLiteralObject, 4 | Type, 5 | } from '@nestjs/common'; 6 | import { ClassTransformOptions, plainToClass } from 'class-transformer'; 7 | import { Document } from 'mongoose'; 8 | 9 | // changes MongoDB documents into instances of the provided class which we use in our controllers 10 | function MongooseClassSerializerInterceptor( 11 | classToIntercept: Type, 12 | ): typeof ClassSerializerInterceptor { 13 | return class Interceptor extends ClassSerializerInterceptor { 14 | private changePlainObjectToClass(document: PlainLiteralObject) { 15 | if (!(document instanceof Document)) { 16 | return document; 17 | } 18 | 19 | return plainToClass(classToIntercept, document.toJSON()); 20 | } 21 | 22 | private prepareResponse( 23 | response: PlainLiteralObject | PlainLiteralObject[], 24 | ) { 25 | if (Array.isArray(response)) { 26 | return response.map(this.changePlainObjectToClass); 27 | } 28 | 29 | return this.changePlainObjectToClass(response); 30 | } 31 | 32 | serialize( 33 | response: PlainLiteralObject | PlainLiteralObject[], 34 | options: ClassTransformOptions, 35 | ) { 36 | return super.serialize(this.prepareResponse(response), options); 37 | } 38 | }; 39 | } 40 | 41 | export default MongooseClassSerializerInterceptor; 42 | -------------------------------------------------------------------------------- /src/utils /override-typescript-rules/eslint.ts: -------------------------------------------------------------------------------- 1 | // 1. Add the following line before the variable assignment for non-type-script packages to 2 | // override the default eslint rules: eslint-disable-next-line @typescript-eslint/no-explicit-any 3 | -------------------------------------------------------------------------------- /src/utils /swagger/constants.ts: -------------------------------------------------------------------------------- 1 | export const SWAGGER_API_ROOT = 'docs'; 2 | export const SWAGGER_API_NAME = 'SATURN API'; 3 | export const SWAGGER_API_DESCRIPTION = 4 | 'This API contains all the endpoints for SATURN'; 5 | export const SWAGGER_API_CURRENT_VERSION = '1.0'; 6 | export const SWAGGER_API_AUTH_NAME = 'Authorization'; 7 | export const SWAGGER_API_AUTH_LOCATION = 'header'; 8 | -------------------------------------------------------------------------------- /src/utils /swagger/index.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; 3 | import { 4 | SWAGGER_API_ROOT, 5 | SWAGGER_API_NAME, 6 | SWAGGER_API_DESCRIPTION, 7 | SWAGGER_API_CURRENT_VERSION, 8 | SWAGGER_API_AUTH_NAME, 9 | SWAGGER_API_AUTH_LOCATION, 10 | } from './constants'; 11 | import { SwaggerDocumentOptions } from './option.type'; 12 | 13 | /** 14 | * @export 15 | * @param {INestApplication} app 16 | * @returns {void} 17 | * @description Setup swagger for the application 18 | * @see {@link https://docs.nestjs.com/controllers#swagger-documentation} 19 | */ 20 | 21 | export function setupSwagger(app: INestApplication): void { 22 | const config = new DocumentBuilder() 23 | .setTitle(SWAGGER_API_NAME) 24 | .setDescription(SWAGGER_API_DESCRIPTION) 25 | .setVersion(SWAGGER_API_CURRENT_VERSION) 26 | .setContact('SATURN', 'https://saturn.com', 'saturn@gmail.com') 27 | .addBearerAuth() 28 | 29 | .build(); 30 | 31 | const options: SwaggerDocumentOptions = { 32 | operationIdFactory: (controllerKey: string, methodKey: string) => 33 | methodKey, 34 | }; 35 | const document = SwaggerModule.createDocument(app, config, options); 36 | SwaggerModule.setup(SWAGGER_API_ROOT, app, document); 37 | } 38 | -------------------------------------------------------------------------------- /src/utils /swagger/option.type.ts: -------------------------------------------------------------------------------- 1 | export interface SwaggerDocumentOptions { 2 | /** 3 | * List of modules to include in the specification 4 | */ 5 | include?: Function[]; 6 | 7 | /** 8 | * Additional, extra models that should be inspected and included in the specification 9 | */ 10 | extraModels?: Function[]; 11 | 12 | /** 13 | * If `true`, swagger will ignore the global prefix set through `setGlobalPrefix()` method 14 | */ 15 | ignoreGlobalPrefix?: boolean; 16 | 17 | /** 18 | * If `true`, swagger will also load routes from the modules imported by `include` modules 19 | */ 20 | deepScanRoutes?: boolean; 21 | 22 | /** 23 | * Custom operationIdFactory that will be used to generate the `operationId` 24 | * based on the `controllerKey` and `methodKey` 25 | * @default () => controllerKey_methodKey 26 | */ 27 | operationIdFactory?: (controllerKey: string, methodKey: string) => string; 28 | } 29 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | --------------------------------------------------------------------------------