├── .gitignore ├── .prettierrc ├── .sequelizerc ├── README.md ├── assets └── logo.png ├── config.ts ├── config ├── config.development.ts └── config.production.ts ├── db ├── config.ts ├── migrations │ └── 20190128160000-create-table-user.js └── seeders-dev │ └── 20190129093300-test-data-users.js ├── nest-cli.json ├── nodemon-debug.json ├── nodemon.json ├── package-lock.json ├── package.json ├── src ├── app.module.ts ├── database │ ├── database.module.ts │ └── database.providers.ts ├── main.ts ├── posts │ ├── dto │ │ ├── create-post.dto.ts │ │ ├── post.dto.ts │ │ └── update-post.dto.ts │ ├── post.entity.ts │ ├── posts.controller.ts │ ├── posts.module.ts │ ├── posts.providers.ts │ └── posts.service.ts ├── shared │ ├── config │ │ └── config.service.ts │ ├── enum │ │ └── gender.ts │ └── shared.module.ts ├── swagger.ts └── users │ ├── auth │ ├── jwt-payload.model.ts │ └── jwt-strategy.ts │ ├── dto │ ├── create-user.dto.ts │ ├── update-user.dto.ts │ ├── user-login-request.dto.ts │ ├── user-login-response.dto.ts │ └── user.dto.ts │ ├── user.entity.ts │ ├── users.controller.ts │ ├── users.module.ts │ ├── users.providers.ts │ └── users.service.ts ├── test ├── app.e2e-spec.ts ├── jest-e2e.json └── test-data.ts ├── tsconfig.build.json ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Output 2 | /dist 3 | 4 | # Dependencies 5 | /node_modules 6 | 7 | # Misc 8 | /coverage 9 | 10 | # System Files 11 | .DS_Store -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "tabWidth": 4, 5 | "semi": true 6 | } 7 | -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | 'config': path.resolve('db', 'config.ts'), 5 | 'seeders-path': path.resolve('db', 'seeders-dev'), 6 | 'migrations-path': path.resolve('db', 'migrations') 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Nest](assets/logo.png) 2 | 3 | ## Description 4 | 5 | Starter kit project made with [Nest](https://github.com/nestjs/nest) that demonstrates CRUD user, JWT authentication, CRUD posts and e2e tests. 6 | 7 | ### Technologies implemented: 8 | 9 | - [sequelize-typescript](https://github.com/RobinBuschmann/sequelize-typescript) (ORM) + [PostgreSQL](https://www.postgresql.org/) 10 | - [JWT](https://jwt.io/) 11 | - [Jest](https://jestjs.io/) 12 | - [Swagger](https://swagger.io/) 13 | 14 | ## Prerequisites 15 | 16 | - [Node.js](https://nodejs.org/) (>= 10.8.0) 17 | - [npm](https://www.npmjs.com/) (>= 6.5.0) 18 | 19 | ## Installation 20 | 21 | ```bash 22 | $ npm install 23 | ``` 24 | 25 | ## Setting up the database for development and test 26 | 27 | PostgreSQL database connection options are shown in the following table: 28 | 29 | | Option | Development | Test | 30 | | -------- | ----------- | --------- | 31 | | Host | localhost | localhost | 32 | | Port | 5432 | 5432 | 33 | | Username | postgres | postgres | 34 | | Password | postgres | postgres | 35 | | Database | nest | nest_test | 36 | 37 | ## Running the app 38 | 39 | ```bash 40 | # development 41 | $ npm run start 42 | 43 | # watch mode 44 | $ npm run start:dev 45 | 46 | # production mode 47 | $ npm run start:prod 48 | ``` 49 | 50 | ## Test 51 | 52 | ```bash 53 | # e2e tests 54 | $ npm run test 55 | ``` 56 | 57 | ## Other commands 58 | 59 | ```bash 60 | # formatting code 61 | $ npm run format 62 | 63 | # run linter 64 | $ npm run lint 65 | 66 | # create database 67 | $ npm run db:create 68 | 69 | # run migrations 70 | $ npm run db:migrate 71 | 72 | # run seeders 73 | $ npm run db:seed-dev 74 | 75 | # reset database 76 | $ npm run db:reset 77 | 78 | # drop database 79 | $ npm run db:drop 80 | 81 | ``` 82 | 83 | ## Run production configuration 84 | 85 | ``` 86 | NODE_ENV=production \ 87 | DATABASE_HOST=db.host.com \ 88 | DATABASE_PORT=5432 \ 89 | DATABASE_USER=user \ 90 | DATABASE_PASSWORD=pass \ 91 | DATABASE_DATABASE=database \ 92 | JWT_PRIVATE_KEY=jwtPrivateKey \ 93 | ts-node -r tsconfig-paths/register src/main.ts 94 | ``` 95 | 96 | ## Swagger API docs 97 | 98 | This project uses the Nest swagger module for API documentation. [NestJS Swagger](https://github.com/nestjs/swagger) - [www.swagger.io](https://swagger.io/) 99 | Swagger docs will be available at localhost:3000/documentation 100 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentloog/nestjs-sequelize-typescript/2f05b0328288bd805e666340c4e5fed2ecbffa93/assets/logo.png -------------------------------------------------------------------------------- /config.ts: -------------------------------------------------------------------------------- 1 | import { config as configDev } from './config/config.development'; 2 | import { config as configProd } from './config/config.production'; 3 | 4 | export default process.env.NODE_ENV === 'production' ? configProd : configDev; 5 | -------------------------------------------------------------------------------- /config/config.development.ts: -------------------------------------------------------------------------------- 1 | import { Dialect } from 'sequelize/types'; 2 | 3 | export const config = { 4 | database: { 5 | dialect: 'postgres' as Dialect, 6 | host: 'localhost', 7 | port: 5432, 8 | username: 'postgres', 9 | password: 'postgres', 10 | database: 'nest', 11 | logging: false, 12 | }, 13 | jwtPrivateKey: 'jwtPrivateKey', 14 | }; 15 | -------------------------------------------------------------------------------- /config/config.production.ts: -------------------------------------------------------------------------------- 1 | import { Dialect } from 'sequelize/types'; 2 | 3 | export const config = { 4 | database: { 5 | dialect: 'postgres' as Dialect, 6 | host: process.env.DATABASE_HOST, 7 | port: +process.env.DATABASE_PORT, 8 | username: process.env.DATABASE_USER, 9 | password: process.env.DATABASE_PASSWORD, 10 | database: process.env.DATABASE_DATABASE, 11 | logging: false, 12 | }, 13 | jwtPrivateKey: process.env.JWT_PRIVATE_KEY, 14 | }; 15 | -------------------------------------------------------------------------------- /db/config.ts: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | 3 | module.exports = config.database; 4 | -------------------------------------------------------------------------------- /db/migrations/20190128160000-create-table-user.js: -------------------------------------------------------------------------------- 1 | const sql = ` 2 | 3 | do $$ 4 | begin 5 | if not exists ( 6 | select 7 | t.typname enum_name 8 | from 9 | pg_type t join 10 | pg_enum e on t.oid = e.enumtypid join 11 | pg_catalog.pg_namespace n on n.oid = t.typnamespace 12 | where 13 | n.nspname = 'public' and 14 | t.typname='enum_user_gender' 15 | group by 1 16 | ) then 17 | create type "public"."enum_user_gender" as enum('female', 'male'); 18 | end if; 19 | end 20 | $$; 21 | 22 | create table "user" ( 23 | "id" uuid, 24 | "email" varchar(255) unique, 25 | "password" varchar(255), 26 | "first_name" varchar(255), 27 | "last_name" varchar(255), 28 | "gender" "public"."enum_user_gender", 29 | "birthday" date, 30 | "created_at" timestamp with time zone, 31 | "updated_at" timestamp with time zone, 32 | "deleted_at" timestamp with time zone, 33 | primary key ("id") 34 | ); 35 | 36 | `; 37 | 38 | module.exports = { 39 | up: queryInterface => queryInterface.sequelize.query(sql), 40 | down: () => {}, 41 | }; 42 | -------------------------------------------------------------------------------- /db/seeders-dev/20190129093300-test-data-users.js: -------------------------------------------------------------------------------- 1 | const sql = ` 2 | create extension if not exists "uuid-ossp"; 3 | 4 | insert into public."user"( 5 | "id", 6 | "email", 7 | "password", 8 | "first_name", 9 | "last_name", 10 | "gender", 11 | "birthday", 12 | "created_at", 13 | "updated_at", 14 | "deleted_at" 15 | ) values ( 16 | uuid_generate_v4(), 17 | 'kentloog@gmail.com', 18 | '$2b$10$9KwTyNlFcEW.Ewtv0s3XRO8YazWojM48qBuED0lMuigsoEIyIXDOO', 19 | 'Kent', 20 | 'Loog', 21 | 'male', 22 | '1996-08-01', 23 | 'now()', 24 | 'now()', 25 | null 26 | ); 27 | `; 28 | 29 | module.exports = { 30 | up: queryInterface => queryInterface.sequelize.query(sql), 31 | down: () => {}, 32 | }; 33 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "ts", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /nodemon-debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "node --inspect-brk -r ts-node/register -r tsconfig-paths/register src/main.ts" 6 | } 7 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "ts-node -r tsconfig-paths/register src/main.ts" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-sequelize-typescript", 3 | "version": "0.0.1", 4 | "description": "Nest + sequelize-typescript + JWT + Jest + Swagger", 5 | "keywords": [ 6 | "nest", 7 | "nestjs", 8 | "nest.js", 9 | "sequelize", 10 | "sequelize-typescript", 11 | "orm", 12 | "nodejs", 13 | "node.js", 14 | "node", 15 | "typescript", 16 | "jwt", 17 | "jsonwebtoken", 18 | "passport", 19 | "swagger", 20 | "jest" 21 | ], 22 | "author": "Kent Loog", 23 | "license": "MIT", 24 | "scripts": { 25 | "build": "tsc -p tsconfig.build.json", 26 | "format": "prettier --write \"src/**/*.ts\"", 27 | "db:migrate": "ts-node node_modules/.bin/sequelize db:migrate", 28 | "db:drop": "ts-node node_modules/.bin/sequelize db:drop", 29 | "db:create": "ts-node node_modules/.bin/sequelize db:create", 30 | "db:seed-dev": "ts-node node_modules/.bin/sequelize db:seed:all", 31 | "db:seed-prod": "ts-node node_modules/.bin/sequelize db:seed:all --seeders-path db/seeders-prod", 32 | "db:reset": "npm run db:drop && npm run db:create && npm run db:migrate && npm run db:seed-dev && npm run db:seed-prod", 33 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 34 | "start:dev": "nodemon", 35 | "start:debug": "nodemon --config nodemon-debug.json", 36 | "prestart:prod": "rimraf dist && npm run build", 37 | "start:prod": "node dist/src/main.js", 38 | "lint": "tslint -p tsconfig.json -c tslint.json", 39 | "test": "jest --config ./test/jest-e2e.json" 40 | }, 41 | "dependencies": { 42 | "@nestjs/common": "^7.0.1", 43 | "@nestjs/core": "^7.0.1", 44 | "@nestjs/passport": "^7.0.0", 45 | "@nestjs/platform-express": "^7.0.1", 46 | "@nestjs/swagger": "^4.4.0", 47 | "bcrypt": "^4.0.1", 48 | "class-transformer": "^0.2.3", 49 | "class-validator": "^0.11.0", 50 | "jsonwebtoken": "^8.5.1", 51 | "passport": "^0.4.1", 52 | "passport-jwt": "^4.0.0", 53 | "pg": "^7.18.2", 54 | "reflect-metadata": "^0.1.13", 55 | "rimraf": "^3.0.2", 56 | "rxjs": "^6.5.4", 57 | "sequelize": "^5.21.5", 58 | "sequelize-cli": "^5.5.1", 59 | "sequelize-typescript": "^1.1.0", 60 | "swagger-ui-express": "^4.1.3" 61 | }, 62 | "devDependencies": { 63 | "@nestjs/testing": "^7.0.1", 64 | "@types/express": "^4.17.3", 65 | "@types/jest": "^25.1.4", 66 | "@types/node": "^13.9.1", 67 | "@types/sequelize": "^4.28.8", 68 | "@types/supertest": "^2.0.8", 69 | "@types/bcrypt": "^3.0.0", 70 | "@types/jsonwebtoken": "^8.3.8", 71 | "@types/passport": "^1.0.3", 72 | "@types/passport-jwt": "^3.0.3", 73 | "jest": "^25.1.0", 74 | "nodemon": "^2.0.2", 75 | "prettier": "^1.19.1", 76 | "supertest": "^4.0.2", 77 | "ts-jest": "^25.2.1", 78 | "ts-node": "^8.6.2", 79 | "tsconfig-paths": "^3.9.0", 80 | "tslint": "6.1.0", 81 | "typescript": "^3.8.3" 82 | }, 83 | "jest": { 84 | "moduleFileExtensions": [ 85 | "js", 86 | "json", 87 | "ts" 88 | ], 89 | "rootDir": "src", 90 | "testRegex": ".spec.ts$", 91 | "transform": { 92 | "^.+\\.(t|j)s$": "ts-jest" 93 | }, 94 | "coverageDirectory": "../coverage", 95 | "testEnvironment": "node" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { UsersModule } from './users/users.module'; 3 | import { SharedModule } from './shared/shared.module'; 4 | import { PostsModule } from './posts/posts.module'; 5 | 6 | @Module({ 7 | imports: [UsersModule, PostsModule, SharedModule], 8 | controllers: [], 9 | providers: [], 10 | }) 11 | export class AppModule {} 12 | -------------------------------------------------------------------------------- /src/database/database.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { databaseProviders } from './database.providers'; 3 | 4 | @Module({ 5 | providers: [...databaseProviders], 6 | exports: [...databaseProviders], 7 | }) 8 | export class DatabaseModule {} 9 | -------------------------------------------------------------------------------- /src/database/database.providers.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize } from 'sequelize-typescript'; 2 | import { User } from './../users/user.entity'; 3 | import { Post } from './../posts/post.entity'; 4 | import { ConfigService } from './../shared/config/config.service'; 5 | 6 | export const databaseProviders = [ 7 | { 8 | provide: 'SEQUELIZE', 9 | useFactory: async (configService: ConfigService) => { 10 | const sequelize = new Sequelize(configService.sequelizeOrmConfig); 11 | sequelize.addModels([User, Post]); 12 | await sequelize.sync(); 13 | return sequelize; 14 | }, 15 | inject: [ConfigService], 16 | }, 17 | ]; 18 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { ValidationPipe } from '@nestjs/common'; 4 | import { setupSwagger } from './swagger'; 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule); 8 | app.useGlobalPipes(new ValidationPipe()); 9 | setupSwagger(app); 10 | await app.listen(3000); 11 | } 12 | 13 | bootstrap(); 14 | -------------------------------------------------------------------------------- /src/posts/dto/create-post.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { Length, IsString } from 'class-validator'; 3 | 4 | export class CreatePostDto { 5 | @ApiProperty() 6 | @IsString() 7 | @Length(3, 60) 8 | readonly title: string; 9 | 10 | @ApiProperty() 11 | @IsString() 12 | readonly content: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/posts/dto/post.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { Post } from '../post.entity'; 3 | 4 | export class PostDto { 5 | @ApiProperty() 6 | readonly id: number; 7 | 8 | @ApiProperty() 9 | readonly authorId: string; 10 | 11 | @ApiProperty() 12 | readonly authorFirstName: string; 13 | 14 | @ApiProperty() 15 | readonly authorLastName: string; 16 | 17 | @ApiProperty() 18 | readonly title: string; 19 | 20 | @ApiProperty() 21 | readonly content: string; 22 | 23 | @ApiProperty() 24 | readonly createdAt: Date; 25 | 26 | @ApiProperty() 27 | readonly updatedAt: Date; 28 | 29 | constructor(post: Post) { 30 | this.id = post.id; 31 | this.authorId = post.userId; 32 | this.authorFirstName = post.user.firstName; 33 | this.authorLastName = post.user.lastName; 34 | this.title = post.title; 35 | this.content = post.content; 36 | this.createdAt = post.createdAt; 37 | this.updatedAt = post.updatedAt; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/posts/dto/update-post.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { Length, IsString, IsOptional } from 'class-validator'; 3 | 4 | export class UpdatePostDto { 5 | @IsOptional() 6 | @ApiProperty() 7 | @IsString() 8 | @Length(3, 60) 9 | readonly title: string; 10 | 11 | @IsOptional() 12 | @ApiProperty() 13 | @IsString() 14 | readonly content: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/posts/post.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | PrimaryKey, 4 | AutoIncrement, 5 | Column, 6 | DataType, 7 | Model, 8 | ForeignKey, 9 | Unique, 10 | Length, 11 | CreatedAt, 12 | UpdatedAt, 13 | DeletedAt, 14 | BelongsTo, 15 | } from 'sequelize-typescript'; 16 | import { User } from './../users/user.entity'; 17 | 18 | @Table({ 19 | tableName: 'post', 20 | }) 21 | export class Post extends Model { 22 | @PrimaryKey 23 | @AutoIncrement 24 | @Column(DataType.BIGINT) 25 | id: number; 26 | 27 | @ForeignKey(() => User) 28 | @Column({ 29 | type: DataType.UUID, 30 | field: 'user_id', 31 | }) 32 | userId: string; 33 | 34 | @Length({ 35 | min: 3, 36 | max: 60, 37 | msg: `The length of post title can't be shorter than 3 and longer than 60 `, 38 | }) 39 | @Column 40 | title: string; 41 | 42 | @Column 43 | content: string; 44 | 45 | @CreatedAt 46 | @Column({ field: 'created_at' }) 47 | createdAt: Date; 48 | 49 | @UpdatedAt 50 | @Column({ field: 'updated_at' }) 51 | updatedAt: Date; 52 | 53 | @DeletedAt 54 | @Column({ field: 'deleted_at' }) 55 | deletedAt: Date; 56 | 57 | @BelongsTo(() => User) 58 | user: User; 59 | } 60 | -------------------------------------------------------------------------------- /src/posts/posts.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Req, 4 | Body, 5 | Post, 6 | UseGuards, 7 | Get, 8 | Param, 9 | ParseIntPipe, 10 | Delete, 11 | Put, 12 | } from '@nestjs/common'; 13 | import { 14 | ApiCreatedResponse, 15 | ApiBearerAuth, 16 | ApiOkResponse, 17 | ApiParam, 18 | ApiTags, 19 | } from '@nestjs/swagger'; 20 | import { CreatePostDto } from './dto/create-post.dto'; 21 | import { PostsService } from './posts.service'; 22 | import { AuthGuard } from '@nestjs/passport'; 23 | import { Post as PostEntity } from './post.entity'; 24 | import { PostDto } from './dto/post.dto'; 25 | import { UpdatePostDto } from './dto/update-post.dto'; 26 | 27 | @Controller('posts') 28 | @ApiTags('posts') 29 | export class PostsController { 30 | constructor(private readonly postsService: PostsService) {} 31 | 32 | @Get() 33 | @ApiOkResponse({ type: [PostDto] }) 34 | findAll(): Promise { 35 | return this.postsService.findAll(); 36 | } 37 | 38 | @Get(':id') 39 | @ApiOkResponse({ type: PostDto }) 40 | @ApiParam({ name: 'id', required: true }) 41 | findOne(@Param('id', new ParseIntPipe()) id: number): Promise { 42 | return this.postsService.findOne(id); 43 | } 44 | 45 | @Post() 46 | @ApiCreatedResponse({ type: PostEntity }) 47 | @ApiBearerAuth() 48 | @UseGuards(AuthGuard('jwt')) 49 | create( 50 | @Body() createPostDto: CreatePostDto, 51 | @Req() request, 52 | ): Promise { 53 | return this.postsService.create(request.user.id, createPostDto); 54 | } 55 | 56 | @Put(':id') 57 | @ApiOkResponse({ type: PostEntity }) 58 | @ApiParam({ name: 'id', required: true }) 59 | @ApiBearerAuth() 60 | @UseGuards(AuthGuard('jwt')) 61 | update( 62 | @Param('id', new ParseIntPipe()) id: number, 63 | @Req() request, 64 | @Body() updatePostDto: UpdatePostDto, 65 | ): Promise { 66 | return this.postsService.update(id, request.user.id, updatePostDto); 67 | } 68 | 69 | @Delete(':id') 70 | @ApiOkResponse({ type: PostEntity }) 71 | @ApiParam({ name: 'id', required: true }) 72 | @ApiBearerAuth() 73 | @UseGuards(AuthGuard('jwt')) 74 | delete( 75 | @Param('id', new ParseIntPipe()) id: number, 76 | @Req() request, 77 | ): Promise { 78 | return this.postsService.delete(id, request.user.id); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/posts/posts.module.ts: -------------------------------------------------------------------------------- 1 | import { DatabaseModule } from './../database/database.module'; 2 | import { Module } from '@nestjs/common'; 3 | import { PostsController } from './posts.controller'; 4 | import { PostsService } from './posts.service'; 5 | import { postsProviders } from './posts.providers'; 6 | 7 | @Module({ 8 | imports: [DatabaseModule], 9 | controllers: [PostsController], 10 | providers: [PostsService, ...postsProviders], 11 | exports: [], 12 | }) 13 | export class PostsModule {} 14 | -------------------------------------------------------------------------------- /src/posts/posts.providers.ts: -------------------------------------------------------------------------------- 1 | import { Post } from './post.entity'; 2 | 3 | export const postsProviders = [{ provide: 'PostsRepository', useValue: Post }]; 4 | -------------------------------------------------------------------------------- /src/posts/posts.service.ts: -------------------------------------------------------------------------------- 1 | import { User } from './../users/user.entity'; 2 | import { Injectable, Inject, HttpException, HttpStatus } from '@nestjs/common'; 3 | import { CreatePostDto } from './dto/create-post.dto'; 4 | import { Post } from './post.entity'; 5 | import { PostDto } from './dto/post.dto'; 6 | import { UpdatePostDto } from './dto/update-post.dto'; 7 | 8 | @Injectable() 9 | export class PostsService { 10 | constructor( 11 | @Inject('PostsRepository') 12 | private readonly postsRepository: typeof Post, 13 | ) {} 14 | 15 | async findAll() { 16 | const posts = await this.postsRepository.findAll({ 17 | include: [User], 18 | }); 19 | return posts.map(post => new PostDto(post)); 20 | } 21 | 22 | async findOne(id: number) { 23 | const post = await this.postsRepository.findByPk(id, { 24 | include: [User], 25 | }); 26 | if (!post) { 27 | throw new HttpException('No post found', HttpStatus.NOT_FOUND); 28 | } 29 | return new PostDto(post); 30 | } 31 | 32 | async create(userId: string, createPostDto: CreatePostDto) { 33 | const post = new Post(); 34 | post.userId = userId; 35 | post.title = createPostDto.title; 36 | post.content = createPostDto.content; 37 | return post.save(); 38 | } 39 | 40 | private async getUserPost(id: number, userId: string) { 41 | const post = await this.postsRepository.findByPk(id); 42 | if (!post) { 43 | throw new HttpException('No post found', HttpStatus.NOT_FOUND); 44 | } 45 | if (post.userId !== userId) { 46 | throw new HttpException( 47 | 'You are unauthorized to manage this post', 48 | HttpStatus.UNAUTHORIZED, 49 | ); 50 | } 51 | 52 | return post; 53 | } 54 | 55 | async update(id: number, userId: string, updatePostDto: UpdatePostDto) { 56 | const post = await this.getUserPost(id, userId); 57 | post.title = updatePostDto.title || post.title; 58 | post.content = updatePostDto.content || post.content; 59 | return post.save(); 60 | } 61 | 62 | async delete(id: number, userId: string) { 63 | const post = await this.getUserPost(id, userId); 64 | await post.destroy(); 65 | return post; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/shared/config/config.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import config from '../../../config'; 3 | 4 | @Injectable() 5 | export class ConfigService { 6 | get sequelizeOrmConfig() { 7 | return config.database; 8 | } 9 | 10 | get jwtConfig() { 11 | return { privateKey: config.jwtPrivateKey }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shared/enum/gender.ts: -------------------------------------------------------------------------------- 1 | export enum Gender { 2 | female = 'female', 3 | male = 'male', 4 | } 5 | -------------------------------------------------------------------------------- /src/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { ConfigService } from './config/config.service'; 3 | 4 | @Global() 5 | @Module({ 6 | providers: [ConfigService], 7 | exports: [ConfigService], 8 | imports: [], 9 | controllers: [], 10 | }) 11 | export class SharedModule {} 12 | -------------------------------------------------------------------------------- /src/swagger.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; 3 | 4 | export function setupSwagger(app: INestApplication) { 5 | const options = new DocumentBuilder() 6 | .setTitle('Nest.js example API') 7 | .setDescription('API Documentation') 8 | .setVersion('1.0') 9 | .addBearerAuth() 10 | .build(); 11 | 12 | const document = SwaggerModule.createDocument(app, options); 13 | SwaggerModule.setup('documentation', app, document); 14 | } 15 | -------------------------------------------------------------------------------- /src/users/auth/jwt-payload.model.ts: -------------------------------------------------------------------------------- 1 | export interface JwtPayload { 2 | email: string; 3 | iat?: Date; 4 | } 5 | -------------------------------------------------------------------------------- /src/users/auth/jwt-strategy.ts: -------------------------------------------------------------------------------- 1 | import { PassportStrategy } from '@nestjs/passport'; 2 | import { Strategy, ExtractJwt, VerifiedCallback } from 'passport-jwt'; 3 | import { JwtPayload } from './jwt-payload.model'; 4 | import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; 5 | import { UsersService } from '../users.service'; 6 | 7 | @Injectable() 8 | export class JwtStrategy extends PassportStrategy(Strategy) { 9 | constructor(private readonly usersService: UsersService) { 10 | super({ 11 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 12 | secretOrKey: 'jwtPrivateKey', 13 | }); 14 | } 15 | 16 | async validate(payload: JwtPayload, done: VerifiedCallback) { 17 | const user = await this.usersService.getUserByEmail(payload.email); 18 | if (!user) { 19 | return done(new HttpException({}, HttpStatus.UNAUTHORIZED), false); 20 | } 21 | 22 | return done(null, user, payload.iat); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/users/dto/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsString, 3 | IsEmail, 4 | IsEnum, 5 | IsISO8601, 6 | IsOptional, 7 | MinLength, 8 | } from 'class-validator'; 9 | import { Gender } from './../../shared/enum/gender'; 10 | import { ApiProperty } from '@nestjs/swagger'; 11 | 12 | export class CreateUserDto { 13 | @ApiProperty() 14 | @IsEmail() 15 | readonly email: string; 16 | 17 | @ApiProperty() 18 | @IsString() 19 | @MinLength(6) 20 | readonly password: string; 21 | 22 | @ApiProperty() 23 | @IsString() 24 | readonly firstName: string; 25 | 26 | @ApiProperty() 27 | @IsString() 28 | readonly lastName: string; 29 | 30 | @ApiProperty() 31 | @IsOptional() 32 | @IsEnum(Gender) 33 | readonly gender: Gender; 34 | 35 | @ApiProperty() 36 | @IsOptional() 37 | @IsISO8601() 38 | readonly birthday: string; 39 | } 40 | -------------------------------------------------------------------------------- /src/users/dto/update-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { Gender } from './../../shared/enum/gender'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | import { IsOptional, IsString, IsEnum, IsISO8601 } from 'class-validator'; 4 | 5 | export class UpdateUserDto { 6 | @ApiProperty() 7 | @IsOptional() 8 | @IsString() 9 | readonly firstName?: string; 10 | 11 | @ApiProperty() 12 | @IsOptional() 13 | @IsString() 14 | readonly lastName?: string; 15 | 16 | @ApiProperty() 17 | @IsOptional() 18 | @IsEnum(Gender) 19 | readonly gender?: Gender; 20 | 21 | @ApiProperty() 22 | @IsOptional() 23 | @IsISO8601() 24 | readonly birthday?: string; 25 | } 26 | -------------------------------------------------------------------------------- /src/users/dto/user-login-request.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsEmail, IsString } from 'class-validator'; 3 | 4 | export class UserLoginRequestDto { 5 | @ApiProperty() 6 | @IsEmail() 7 | readonly email: string; 8 | 9 | @ApiProperty() 10 | @IsString() 11 | readonly password: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/users/dto/user-login-response.dto.ts: -------------------------------------------------------------------------------- 1 | import { UserDto } from './user.dto'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | import { User } from '../user.entity'; 4 | 5 | export class UserLoginResponseDto extends UserDto { 6 | @ApiProperty() 7 | token: string; 8 | 9 | constructor(user: User, token?: string) { 10 | super(user); 11 | this.token = token; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/users/dto/user.dto.ts: -------------------------------------------------------------------------------- 1 | import { User } from './../user.entity'; 2 | import { Gender } from './../../shared/enum/gender'; 3 | import { ApiProperty } from '@nestjs/swagger'; 4 | 5 | export class UserDto { 6 | @ApiProperty() 7 | id: string; 8 | 9 | @ApiProperty() 10 | readonly email: string; 11 | 12 | @ApiProperty() 13 | readonly firstName: string; 14 | 15 | @ApiProperty() 16 | readonly lastName: string; 17 | 18 | @ApiProperty() 19 | readonly gender: Gender; 20 | 21 | @ApiProperty() 22 | readonly birthday: string; 23 | 24 | constructor(user: User) { 25 | this.id = user.id; 26 | this.email = user.email; 27 | this.firstName = user.firstName; 28 | this.lastName = user.lastName; 29 | this.gender = user.gender; 30 | this.birthday = user.birthday; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/users/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Column, 4 | Model, 5 | Unique, 6 | IsEmail, 7 | DataType, 8 | CreatedAt, 9 | UpdatedAt, 10 | DeletedAt, 11 | HasMany, 12 | } from 'sequelize-typescript'; 13 | import { Gender } from './../shared/enum/gender'; 14 | import { Post } from './../posts/post.entity'; 15 | 16 | @Table({ 17 | tableName: 'user', 18 | }) 19 | export class User extends Model { 20 | @Column({ 21 | type: DataType.UUID, 22 | defaultValue: DataType.UUIDV4, 23 | primaryKey: true, 24 | }) 25 | id: string; 26 | 27 | @Unique 28 | @IsEmail 29 | @Column 30 | email: string; 31 | 32 | @Column 33 | password: string; 34 | 35 | @Column({ field: 'first_name' }) 36 | firstName: string; 37 | 38 | @Column({ field: 'last_name' }) 39 | lastName: string; 40 | 41 | @Column({ type: DataType.ENUM(Gender.female, Gender.male) }) 42 | gender: Gender; 43 | 44 | @Column(DataType.DATEONLY) 45 | birthday: string; 46 | 47 | @CreatedAt 48 | @Column({ field: 'created_at' }) 49 | createdAt: Date; 50 | 51 | @UpdatedAt 52 | @Column({ field: 'updated_at' }) 53 | updatedAt: Date; 54 | 55 | @DeletedAt 56 | @Column({ field: 'deleted_at' }) 57 | deletedAt: Date; 58 | 59 | @HasMany(() => Post) 60 | posts: Post[]; 61 | } 62 | -------------------------------------------------------------------------------- /src/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { UserLoginRequestDto } from './dto/user-login-request.dto'; 2 | import { 3 | Controller, 4 | Get, 5 | Post, 6 | Body, 7 | HttpCode, 8 | Delete, 9 | Req, 10 | UseGuards, 11 | Put, 12 | } from '@nestjs/common'; 13 | import { CreateUserDto } from './dto/create-user.dto'; 14 | import { UsersService } from './users.service'; 15 | import { UserDto } from './dto/user.dto'; 16 | import { ApiTags, ApiOkResponse, ApiBearerAuth } from '@nestjs/swagger'; 17 | import { UserLoginResponseDto } from './dto/user-login-response.dto'; 18 | import { AuthGuard } from '@nestjs/passport'; 19 | import { UpdateUserDto } from './dto/update-user.dto'; 20 | 21 | @Controller('users') 22 | @ApiTags('users') 23 | export class UsersController { 24 | constructor(private readonly usersService: UsersService) {} 25 | 26 | @Post('register') 27 | @ApiOkResponse({ type: UserLoginResponseDto }) 28 | register( 29 | @Body() createUserDto: CreateUserDto, 30 | ): Promise { 31 | return this.usersService.create(createUserDto); 32 | } 33 | 34 | @Post('login') 35 | @HttpCode(200) 36 | @ApiOkResponse({ type: UserLoginResponseDto }) 37 | login( 38 | @Body() userLoginRequestDto: UserLoginRequestDto, 39 | ): Promise { 40 | return this.usersService.login(userLoginRequestDto); 41 | } 42 | 43 | @Get() 44 | @ApiBearerAuth() 45 | @UseGuards(AuthGuard('jwt')) 46 | @ApiOkResponse({ type: [UserDto] }) 47 | findAll(): Promise { 48 | return this.usersService.findAll(); 49 | } 50 | 51 | @Get('me') 52 | @ApiBearerAuth() 53 | @UseGuards(AuthGuard('jwt')) 54 | @ApiOkResponse({ type: UserDto }) 55 | async getUser(@Req() request): Promise { 56 | return this.usersService.getUser(request.user.id); 57 | } 58 | 59 | @Put('me') 60 | @ApiBearerAuth() 61 | @UseGuards(AuthGuard('jwt')) 62 | @ApiOkResponse({ type: UserDto }) 63 | update( 64 | @Body() updateUserDto: UpdateUserDto, 65 | @Req() request, 66 | ): Promise { 67 | return this.usersService.update(request.user.id, updateUserDto); 68 | } 69 | 70 | @Delete('me') 71 | @ApiBearerAuth() 72 | @UseGuards(AuthGuard('jwt')) 73 | @ApiOkResponse({ type: UserDto }) 74 | delete(@Req() request): Promise { 75 | return this.usersService.delete(request.user.id); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { UsersController } from './users.controller'; 3 | import { usersProviders } from './users.providers'; 4 | import { DatabaseModule } from './../database/database.module'; 5 | import { UsersService } from './users.service'; 6 | import { JwtStrategy } from './auth/jwt-strategy'; 7 | 8 | @Module({ 9 | imports: [DatabaseModule], 10 | controllers: [UsersController], 11 | providers: [UsersService, ...usersProviders, JwtStrategy], 12 | exports: [UsersService], 13 | }) 14 | export class UsersModule {} 15 | -------------------------------------------------------------------------------- /src/users/users.providers.ts: -------------------------------------------------------------------------------- 1 | import { User } from './user.entity'; 2 | 3 | export const usersProviders = [{ provide: 'UsersRepository', useValue: User }]; 4 | -------------------------------------------------------------------------------- /src/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject, HttpException, HttpStatus } from '@nestjs/common'; 2 | import { User } from './user.entity'; 3 | import { genSalt, hash, compare } from 'bcrypt'; 4 | import { UserDto } from './dto/user.dto'; 5 | import { UserLoginRequestDto } from './dto/user-login-request.dto'; 6 | import { CreateUserDto } from './dto/create-user.dto'; 7 | import { UserLoginResponseDto } from './dto/user-login-response.dto'; 8 | import { JwtPayload } from './auth/jwt-payload.model'; 9 | import { sign } from 'jsonwebtoken'; 10 | import { UpdateUserDto } from './dto/update-user.dto'; 11 | import { ConfigService } from './../shared/config/config.service'; 12 | 13 | @Injectable() 14 | export class UsersService { 15 | private readonly jwtPrivateKey: string; 16 | 17 | constructor( 18 | @Inject('UsersRepository') 19 | private readonly usersRepository: typeof User, 20 | private readonly configService: ConfigService, 21 | ) { 22 | this.jwtPrivateKey = this.configService.jwtConfig.privateKey; 23 | } 24 | 25 | async findAll() { 26 | const users = await this.usersRepository.findAll(); 27 | return users.map(user => new UserDto(user)); 28 | } 29 | 30 | async getUser(id: string) { 31 | const user = await this.usersRepository.findByPk(id); 32 | if (!user) { 33 | throw new HttpException( 34 | 'User with given id not found', 35 | HttpStatus.NOT_FOUND, 36 | ); 37 | } 38 | return new UserDto(user); 39 | } 40 | 41 | async getUserByEmail(email: string) { 42 | return await this.usersRepository.findOne({ 43 | where: { email }, 44 | }); 45 | } 46 | 47 | async create(createUserDto: CreateUserDto) { 48 | try { 49 | const user = new User(); 50 | user.email = createUserDto.email.trim().toLowerCase(); 51 | user.firstName = createUserDto.firstName; 52 | user.lastName = createUserDto.lastName; 53 | user.gender = createUserDto.gender; 54 | user.birthday = createUserDto.birthday; 55 | 56 | const salt = await genSalt(10); 57 | user.password = await hash(createUserDto.password, salt); 58 | 59 | const userData = await user.save(); 60 | 61 | // when registering then log user in automatically by returning a token 62 | const token = await this.signToken(userData); 63 | return new UserLoginResponseDto(userData, token); 64 | } catch (err) { 65 | if (err.original.constraint === 'user_email_key') { 66 | throw new HttpException( 67 | `User with email '${err.errors[0].value}' already exists`, 68 | HttpStatus.CONFLICT, 69 | ); 70 | } 71 | 72 | throw new HttpException(err, HttpStatus.INTERNAL_SERVER_ERROR); 73 | } 74 | } 75 | 76 | async login(userLoginRequestDto: UserLoginRequestDto) { 77 | const email = userLoginRequestDto.email; 78 | const password = userLoginRequestDto.password; 79 | 80 | const user = await this.getUserByEmail(email); 81 | if (!user) { 82 | throw new HttpException( 83 | 'Invalid email or password.', 84 | HttpStatus.BAD_REQUEST, 85 | ); 86 | } 87 | 88 | const isMatch = await compare(password, user.password); 89 | if (!isMatch) { 90 | throw new HttpException( 91 | 'Invalid email or password.', 92 | HttpStatus.BAD_REQUEST, 93 | ); 94 | } 95 | 96 | const token = await this.signToken(user); 97 | return new UserLoginResponseDto(user, token); 98 | } 99 | 100 | async update(id: string, updateUserDto: UpdateUserDto) { 101 | const user = await this.usersRepository.findByPk(id); 102 | if (!user) { 103 | throw new HttpException('User not found.', HttpStatus.NOT_FOUND); 104 | } 105 | 106 | user.firstName = updateUserDto.firstName || user.firstName; 107 | user.lastName = updateUserDto.lastName || user.lastName; 108 | user.gender = updateUserDto.gender || user.gender; 109 | user.birthday = updateUserDto.birthday || user.birthday; 110 | 111 | try { 112 | const data = await user.save(); 113 | return new UserDto(data); 114 | } catch (err) { 115 | throw new HttpException(err, HttpStatus.INTERNAL_SERVER_ERROR); 116 | } 117 | } 118 | 119 | async delete(id: string) { 120 | const user = await this.usersRepository.findByPk(id); 121 | await user.destroy(); 122 | return new UserDto(user); 123 | } 124 | 125 | async signToken(user: User) { 126 | const payload: JwtPayload = { 127 | email: user.email, 128 | }; 129 | 130 | return sign(payload, this.jwtPrivateKey, {}); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from '@nestjs/testing'; 2 | import request from 'supertest'; 3 | import { AppModule } from './../src/app.module'; 4 | import { INestApplication, ValidationPipe, HttpStatus } from '@nestjs/common'; 5 | import { Sequelize } from 'sequelize-typescript'; 6 | import { ConfigService } from './../src/shared/config/config.service'; 7 | import { Post } from './../src/posts/post.entity'; 8 | import { User } from './../src/users/user.entity'; 9 | import { 10 | createUserDto1, 11 | createUserDto2, 12 | userLoginResponseDto1, 13 | createUserDto3, 14 | createUserDto4, 15 | createUserDto5, 16 | userLoginRequestDto1, 17 | userLoginRequestDto2, 18 | userLoginRequestDto3, 19 | } from './test-data'; 20 | 21 | describe('/', () => { 22 | let app: INestApplication; 23 | let sequelize: Sequelize; 24 | let userId: string; 25 | let token: string; 26 | 27 | beforeAll(async () => { 28 | const module = await Test.createTestingModule({ 29 | imports: [AppModule], 30 | providers: [ 31 | { 32 | provide: 'SEQUELIZE', 33 | useFactory: (configService: ConfigService) => { 34 | sequelize = new Sequelize( 35 | configService.sequelizeOrmConfig, 36 | ); 37 | 38 | sequelize.addModels([User, Post]); 39 | 40 | return sequelize; 41 | }, 42 | inject: [ConfigService], 43 | }, 44 | ], 45 | }).compile(); 46 | 47 | app = module.createNestApplication(); 48 | app.useGlobalPipes(new ValidationPipe()); 49 | await app.init(); 50 | }); 51 | 52 | describe('/users', () => { 53 | describe('POST /register', () => { 54 | it('should return 400 if email is not valid', () => { 55 | return request(app.getHttpServer()) 56 | .post('/users/register') 57 | .send(createUserDto3) 58 | .expect(HttpStatus.BAD_REQUEST); 59 | }); 60 | 61 | it('should return 400 if birthday is not ISO 8601 date string', () => { 62 | return request(app.getHttpServer()) 63 | .post('/users/register') 64 | .send(createUserDto4) 65 | .expect(HttpStatus.BAD_REQUEST); 66 | }); 67 | 68 | it('should return 400 if gender is not a valid enum value', () => { 69 | return request(app.getHttpServer()) 70 | .post('/users/register') 71 | .send(createUserDto5) 72 | .expect(HttpStatus.BAD_REQUEST); 73 | }); 74 | 75 | it('should return 400 if any of the required fields is missing', () => { 76 | return request(app.getHttpServer()) 77 | .post('/users/register') 78 | .send(createUserDto2) 79 | .expect(HttpStatus.BAD_REQUEST); 80 | }); 81 | 82 | it('should return 201 if user is created', () => { 83 | return request(app.getHttpServer()) 84 | .post('/users/register') 85 | .send(createUserDto1) 86 | .expect(HttpStatus.CREATED) 87 | .expect(res => { 88 | userId = res.body.id; 89 | userLoginResponseDto1.id = res.body.id; 90 | userLoginResponseDto1.token = res.body.token; 91 | expect(res.body).toEqual(userLoginResponseDto1); 92 | }); 93 | }); 94 | 95 | it('should return 409 if user with given email already exists', () => { 96 | return request(app.getHttpServer()) 97 | .post('/users/register') 98 | .send(createUserDto1) 99 | .expect(HttpStatus.CONFLICT); 100 | }); 101 | }); 102 | 103 | describe('POST /login', () => { 104 | it('should return 200 and jwt token', () => { 105 | return request(app.getHttpServer()) 106 | .post('/users/login') 107 | .send(userLoginRequestDto1) 108 | .expect(HttpStatus.OK) 109 | .expect(res => { 110 | token = res.body.token; 111 | userLoginResponseDto1.id = res.body.id; 112 | userLoginResponseDto1.token = token; 113 | expect(res.body).toEqual(userLoginResponseDto1); 114 | }); 115 | }); 116 | 117 | it('should return 400 when user with given email not found', () => { 118 | return request(app.getHttpServer()) 119 | .post('/users/login') 120 | .send(userLoginRequestDto2) 121 | .expect(HttpStatus.BAD_REQUEST); 122 | }); 123 | 124 | it('should return 400 when wrong password inserted', () => { 125 | return request(app.getHttpServer()) 126 | .post('/users/login') 127 | .send(userLoginRequestDto3) 128 | .expect(HttpStatus.BAD_REQUEST); 129 | }); 130 | it('should return 400 when no data sent', () => { 131 | return request(app.getHttpServer()) 132 | .post('/users/login') 133 | .send({}) 134 | .expect(HttpStatus.BAD_REQUEST); 135 | }); 136 | }); 137 | }); 138 | 139 | afterAll(async done => { 140 | await app.close(); 141 | await sequelize.drop(); 142 | sequelize.close(); 143 | done(); 144 | }); 145 | }); 146 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/test-data.ts: -------------------------------------------------------------------------------- 1 | import { UserLoginRequestDto } from './../src/users/dto/user-login-request.dto'; 2 | import { UpdateUserDto } from './../src/users/dto/update-user.dto'; 3 | import { UserLoginResponseDto } from './../src/users/dto/user-login-response.dto'; 4 | import { UserDto } from './../src/users/dto/user.dto'; 5 | import { Gender } from './../src/shared/enum/gender'; 6 | import { CreateUserDto } from './../src/users/dto/create-user.dto'; 7 | 8 | export const createUserDto1: CreateUserDto = { 9 | email: 'testemail@gmail.com', 10 | password: 'password123', 11 | firstName: 'John', 12 | lastName: 'Smith', 13 | gender: Gender.male, 14 | birthday: '1986-07-17', 15 | }; 16 | 17 | export const createUserDto2 = { 18 | email: 'testemail@gmail.com', 19 | password: 'password123', 20 | lastName: 'Smith', 21 | gender: Gender.male, 22 | birthday: '1986-07-17', 23 | }; 24 | 25 | export const createUserDto3 = { 26 | ...createUserDto1, 27 | email: 'not-email', 28 | }; 29 | 30 | export const createUserDto4 = { 31 | ...createUserDto1, 32 | birthday: 'not-valid-date', 33 | }; 34 | 35 | export const createUserDto5 = { 36 | ...createUserDto1, 37 | gender: 'not-valid-gender', 38 | }; 39 | 40 | export const userLoginRequestDto1: UserLoginRequestDto = { 41 | email: createUserDto1.email, 42 | password: createUserDto1.password, 43 | }; 44 | 45 | export const userLoginRequestDto2: UserLoginRequestDto = { 46 | email: 'wrong-email', 47 | password: createUserDto1.password, 48 | }; 49 | 50 | export const userLoginRequestDto3: UserLoginRequestDto = { 51 | email: 'wrong-email', 52 | password: createUserDto1.password, 53 | }; 54 | 55 | export const userDto1: UserDto = { 56 | id: 'uuid/v4', 57 | email: 'testemail@gmail.com', 58 | firstName: 'John', 59 | lastName: 'Smith', 60 | gender: Gender.male, 61 | birthday: '1986-07-17', 62 | }; 63 | 64 | export const userLoginResponseDto1: UserLoginResponseDto = { 65 | ...userDto1, 66 | token: 'token', 67 | }; 68 | 69 | export const updateUserDto1: UpdateUserDto = { 70 | gender: Gender.female, 71 | birthday: '1996-07-17', 72 | }; 73 | 74 | export const userDto2: UserDto = { 75 | ...userDto1, 76 | gender: Gender.female, 77 | birthday: '1996-07-17', 78 | }; 79 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*"], 4 | "exclude": ["node_modules", "test", "**/*spec.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "allowSyntheticDefaultImports": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es6", 12 | "sourceMap": true, 13 | "outDir": "./dist", 14 | "baseUrl": "./", 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true 17 | }, 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false 16 | }, 17 | "rulesDirectory": [], 18 | "linterOptions": { 19 | "exclude": ["config/**"] 20 | } 21 | } 22 | --------------------------------------------------------------------------------