├── .commitlintrc.js ├── .dockerignore ├── .gitignore ├── .prettierrc ├── Dockerfile ├── LICENSE ├── README.md ├── migrations └── 1563725408398-update-cat.ts ├── nest-cli.json ├── nodemon-debug.json ├── nodemon.json ├── ormconfig.json ├── package-lock.json ├── package.json ├── src ├── app.module.ts ├── config │ ├── index.ts │ └── prod.config.ts ├── core │ ├── filter │ │ └── errors.filter.ts │ ├── interceptor │ │ └── transform.interceptor.ts │ ├── middleware │ │ └── logger.middleware.ts │ └── pipe │ │ └── validation.pipe.ts ├── features │ ├── apis │ │ ├── account │ │ │ ├── account.controller.ts │ │ │ └── account.service.ts │ │ ├── auth │ │ │ ├── auth.service.ts │ │ │ └── jwt.strategy.ts │ │ ├── cats │ │ │ ├── cats.controller.ts │ │ │ └── cats.service.ts │ │ └── dogs │ │ │ ├── dogs.controller.ts │ │ │ └── dogs.service.ts │ ├── dtos │ │ ├── account.dto.ts │ │ └── cat.dto.ts │ ├── entities │ │ ├── cat.entity.ts │ │ ├── common.entity.ts │ │ ├── dog.entity.ts │ │ └── user.entity.ts │ ├── features.module.ts │ └── interfaces │ │ └── auth.interface.ts ├── main.ts └── shared │ ├── services │ └── lunar-calendar │ │ └── lunar-calendar.service.ts │ ├── shared.module.ts │ └── utils │ └── logger.ts ├── static └── .gitkeep ├── test ├── app.e2e-spec.ts └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json ├── tslint.json └── views └── catsPage.hbs /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | // type: type of commit 2 | // feat: A new feature 3 | // fix: A bug fix 4 | // docs: Documentation only changes 5 | // style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 6 | // refactor: A code change that neither fixes a bug nor adds a feature 7 | // perf: A code change that improves performance 8 | // test: Adding missing tests or correcting existing tests 9 | // build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) 10 | // ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) 11 | // revert: Reverts a previous commit 12 | // chore: Other changes that don't modify src or test files 13 | // subject: description of commit, 50/72 formatting 14 | 15 | module.exports = { 16 | extends: ['@commitlint/config-conventional'], 17 | rules: { 18 | 'type-enum': [ 19 | 2, 20 | 'always', 21 | ["feat", "fix", "docs", "style", "refactor", "chore", "publish", "perf", "revert", "test", "build", "ci"] 22 | ], 23 | 'subject-case': [0, 'never'], 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "trailingComma": "all", 5 | "tabWidth": 2, 6 | "useTabs": false 7 | } 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10.15.3 2 | 3 | WORKDIR /app 4 | 5 | COPY . /app 6 | 7 | RUN npm i pm2 -g \ 8 | && npm i \ 9 | && npm run build 10 | 11 | EXPOSE 3300 12 | 13 | CMD ["npm", "prod"] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 tc9011 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master 6 | [travis-url]: https://travis-ci.org/nestjs/nest 7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux 8 | [linux-url]: https://travis-ci.org/nestjs/nest 9 | 10 |

A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.

11 |

12 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Travis 16 | Linux 17 | Coverage 18 | Gitter 19 | Backers on Open Collective 20 | Sponsors on Open Collective 21 | 22 | 23 |

24 | 26 | 27 | ## Description 28 | 29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 30 | 31 | ## Installation 32 | 33 | ```bash 34 | $ npm install 35 | ``` 36 | 37 | ## MySQL 38 | * run mysql 39 | 40 | ```bash 41 | docker run --name mysql -e MYSQL_ROOT_PASSWORD=123456 -d -p 3310:3306 mysql:8.0.0 42 | ``` 43 | 44 | * create mysql database 45 | 46 | ```bash 47 | # password: 123456 48 | mysql -h 127.0.0.1 -uroot -P 3310 -p 49 | CREATE DATABASE IF NOT EXISTS test CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 50 | ``` 51 | 52 | ## Running the app 53 | 54 | ```bash 55 | # development 56 | $ npm run start 57 | 58 | # watch mode 59 | $ npm run start:dev 60 | 61 | # production mode 62 | $ npm run start:prod 63 | ``` 64 | 65 | ## Migration 66 | 67 | ``` bash 68 | npm run migration:run 69 | ``` 70 | 71 | ## Test 72 | 73 | ```bash 74 | # unit tests 75 | $ npm run test 76 | 77 | # e2e tests 78 | $ npm run test:e2e 79 | 80 | # test coverage 81 | $ npm run test:cov 82 | ``` 83 | 84 | ## Support 85 | 86 | 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). 87 | 88 | ## Stay in touch 89 | 90 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 91 | - Website - [https://nestjs.com](https://nestjs.com/) 92 | - Twitter - [@nestframework](https://twitter.com/nestframework) 93 | 94 | ## License 95 | 96 | Nest is [MIT licensed](LICENSE). 97 | -------------------------------------------------------------------------------- /migrations/1563725408398-update-cat.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class updateCat1563725408398 implements MigrationInterface { 4 | 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.query(`insert into cat (id, name, age, breed) values (2, 'test', 3, 'cat') `) 7 | } 8 | 9 | public async down(queryRunner: QueryRunner): Promise { 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /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": "*", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "ts-node -r tsconfig-paths/register src/main.ts" 6 | } 7 | -------------------------------------------------------------------------------- /ormconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "mysql", 3 | "host": "localhost", 4 | "port": 3310, 5 | "username": "root", 6 | "password": "123456", 7 | "database": "test", 8 | "entities": ["./**/*.entity.ts"], 9 | "migrations": ["migrations/*.ts"], 10 | "cli": { 11 | "migrationsDir": "migrations" 12 | }, 13 | "timezone": "UTC", 14 | "charset": "utf8mb4", 15 | "multipleStatements": true, 16 | "dropSchema": false, 17 | "synchronize": false, 18 | "logging": true 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awesome-nest", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "license": "MIT", 7 | "scripts": { 8 | "prod": "cross-env NODE_ENV=production pm2 start dist/main.js --name 'awesome-nest'", 9 | "uat": "cross-env NODE_ENV=uat pm2 start dist/main.js --name 'awesome-nest'", 10 | "stop": "pm2 delete awesome-nest", 11 | "build": "tsc -p tsconfig.build.json", 12 | "format": "prettier --config .prettierrc --write \"src/**/*.ts\"", 13 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 14 | "start:dev": "concurrently --handle-input \"wait-on dist/main.js && nodemon\" \"tsc -w -p tsconfig.build.json\" ", 15 | "start:debug": "nodemon --config nodemon-debug.json", 16 | "prestart:prod": "rimraf dist && npm run build", 17 | "start:prod": "cross-env NODE_ENV=production node dist/main.js", 18 | "lint": "tslint -p tsconfig.json -c tslint.json", 19 | "lint:fix": "tslint -p tsconfig.json -c tslint.json 'src/**/*.ts' --fix", 20 | "test": "jest", 21 | "test:watch": "jest --watch", 22 | "test:cov": "jest --coverage", 23 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 24 | "test:e2e": "jest --config ./test/jest-e2e.json", 25 | "migration:generate": "ts-node node_modules/.bin/typeorm migration:generate", 26 | "migration:create": "ts-node node_modules/.bin/typeorm migration:crteate", 27 | "migration:run": "ts-node node_modules/.bin/typeorm migration:run", 28 | "migration:revert": "ts-node node_modules/.bin/typeorm migration:revert", 29 | "commit": "git-cz" 30 | }, 31 | "dependencies": { 32 | "@nestjs/common": "^6.0.0", 33 | "@nestjs/core": "^6.0.0", 34 | "@nestjs/jwt": "^6.1.1", 35 | "@nestjs/passport": "^6.1.0", 36 | "@nestjs/platform-express": "^6.0.0", 37 | "@nestjs/swagger": "^3.1.0", 38 | "@nestjs/typeorm": "^6.1.3", 39 | "@nestjsx/crud": "^4.1.0", 40 | "@nestjsx/crud-typeorm": "^4.1.0", 41 | "bcryptjs": "^2.4.3", 42 | "class-transformer": "^0.2.3", 43 | "class-validator": "^0.9.1", 44 | "express-rate-limit": "^5.0.0", 45 | "hbs": "^4.0.4", 46 | "helmet": "^3.20.0", 47 | "lodash": "^4.17.15", 48 | "log4js": "^4.5.1", 49 | "moment": "^2.24.0", 50 | "mysql": "^2.17.1", 51 | "passport": "^0.4.0", 52 | "passport-jwt": "^4.0.0", 53 | "reflect-metadata": "^0.1.12", 54 | "rimraf": "^2.6.2", 55 | "rxjs": "^6.3.3", 56 | "stacktrace-js": "^2.0.0", 57 | "swagger-ui-express": "^4.0.7", 58 | "typeorm": "^0.2.18", 59 | "youch": "^2.0.10" 60 | }, 61 | "devDependencies": { 62 | "@commitlint/cli": "^8.1.0", 63 | "@commitlint/config-conventional": "^8.1.0", 64 | "@leo-tools/tslint-config-common": "^0.0.3", 65 | "@nestjs/testing": "^6.0.0", 66 | "@types/express": "^4.16.0", 67 | "@types/jest": "^23.3.13", 68 | "@types/node": "^10.12.18", 69 | "@types/supertest": "^2.0.7", 70 | "commitizen": "^4.0.3", 71 | "concurrently": "^4.1.0", 72 | "cross-env": "^5.2.0", 73 | "cz-conventional-changelog": "^3.0.2", 74 | "husky": "^3.0.2", 75 | "jest": "^23.6.0", 76 | "lint-staged": "^9.2.1", 77 | "nodemon": "^1.18.9", 78 | "prettier": "^1.15.3", 79 | "supertest": "^3.4.1", 80 | "ts-jest": "24.0.2", 81 | "ts-node": "8.1.0", 82 | "tsconfig-paths": "3.8.0", 83 | "tslint": "5.16.0", 84 | "tslint-config-prettier": "^1.18.0", 85 | "typescript": "3.4.3", 86 | "wait-on": "^3.2.0" 87 | }, 88 | "jest": { 89 | "moduleFileExtensions": [ 90 | "js", 91 | "json", 92 | "ts" 93 | ], 94 | "roots": [ 95 | "src", 96 | "test" 97 | ], 98 | "testRegex": ".spec.ts$", 99 | "transform": { 100 | "^.+\\.(t|j)s$": "ts-jest" 101 | }, 102 | "coverageDirectory": "../coverage", 103 | "coveragePathIgnorePatterns": [ 104 | "/test/", 105 | "/node_modules/", 106 | "/src/config/" 107 | ], 108 | "testEnvironment": "node" 109 | }, 110 | "husky": { 111 | "hooks": { 112 | "pre-commit": "lint-staged", 113 | "commit-msg": "commitlint -e $GIT_PARAMS" 114 | } 115 | }, 116 | "lint-staged": { 117 | "src/**/*.ts": [ 118 | "npm run format", 119 | "npm run lint:fix", 120 | "git add" 121 | ] 122 | }, 123 | "config": { 124 | "commitizen": { 125 | "path": "node_modules/cz-conventional-changelog" 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | 3 | import { FeaturesModule } from './features/features.module' 4 | import { SharedModule } from './shared/shared.module' 5 | 6 | @Module({ 7 | imports: [SharedModule, FeaturesModule], 8 | controllers: [], 9 | providers: [], 10 | }) 11 | export class AppModule {} 12 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash' 2 | import { resolve } from 'path' 3 | 4 | import productionConfig from './prod.config' 5 | 6 | export const isProd = process.env.NODE_ENV === 'production' 7 | export const isUat = process.env.NODE_ENV === 'uat' 8 | 9 | let config = { 10 | port: 3000, 11 | hostName: 'localhost', 12 | 13 | orm: { 14 | type: 'mysql', 15 | host: 'localhost', 16 | port: 3310, 17 | username: 'root', 18 | password: '123456', 19 | database: 'test', 20 | entities: [resolve(`./**/*.entity${isUat ? '.js' : '.ts'}`)], 21 | migrations: ['migration/*.ts'], 22 | timezone: 'UTC', 23 | charset: 'utf8mb4', 24 | multipleStatements: true, 25 | dropSchema: false, 26 | synchronize: true, 27 | logging: true, 28 | }, 29 | 30 | jwt: { 31 | secret: 'secretKey', 32 | signOptions: { 33 | expiresIn: 60 * 60 * 24 * 30, 34 | }, 35 | }, 36 | } 37 | 38 | if (isProd) { 39 | config = _.merge(config, productionConfig) 40 | } 41 | 42 | export { config } 43 | export default config 44 | -------------------------------------------------------------------------------- /src/config/prod.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | 3 | export default { 4 | port: 3210, 5 | 6 | orm: { 7 | type: 'mysql', 8 | host: 'localhost', 9 | port: 3310, 10 | username: 'root', 11 | password: '123456', 12 | database: 'test', 13 | entities: [resolve('./**/*.entity.js')], 14 | migrations: ['migration/*.ts'], 15 | dropSchema: false, 16 | synchronize: false, 17 | logging: false, 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /src/core/filter/errors.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArgumentsHost, 3 | Catch, 4 | ExceptionFilter, 5 | HttpException, 6 | HttpStatus, 7 | } from '@nestjs/common' 8 | import * as Youch from 'youch' 9 | 10 | import { isProd } from '../../config' 11 | import { Logger } from '../../shared/utils/logger' 12 | 13 | @Catch() 14 | export class ExceptionsFilter implements ExceptionFilter { 15 | async catch(exception, host: ArgumentsHost) { 16 | const ctx = host.switchToHttp() 17 | const response = ctx.getResponse() 18 | const request = ctx.getRequest() 19 | 20 | Logger.error('exception', JSON.stringify(exception)) 21 | 22 | let message = exception.message 23 | let isDeepestMessage = false 24 | while (!isDeepestMessage) { 25 | isDeepestMessage = !message.message 26 | message = isDeepestMessage ? message : message.message 27 | } 28 | 29 | const errorResponse = { 30 | message: message || '请求失败', 31 | status: 1, 32 | } 33 | 34 | if (exception instanceof HttpException) { 35 | const status = exception.getStatus() 36 | Logger.error( 37 | `Catch http exception at ${request.method} ${request.url} ${status}`, 38 | ) 39 | 40 | response.status(status) 41 | response.header('Content-Type', 'application/json; charset=utf-8') 42 | response.send(errorResponse) 43 | } else { 44 | if (!isProd) { 45 | const youch = new Youch(exception, request) 46 | 47 | const html = await youch 48 | .addLink(link => { 49 | const url = `https://stackoverflow.com/search?q=${encodeURIComponent( 50 | `[adonis.js] ${link.message}`, 51 | )}` 52 | return `Search StackOverflow` 53 | }) 54 | .toHTML() 55 | 56 | response.type('text/html') 57 | response.status(HttpStatus.INTERNAL_SERVER_ERROR) 58 | response.send(html) 59 | } else { 60 | response.status(HttpStatus.INTERNAL_SERVER_ERROR) 61 | response.header('Content-Type', 'application/json; charset=utf-8') 62 | response.send(errorResponse) 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/core/interceptor/transform.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CallHandler, 3 | ExecutionContext, 4 | Injectable, 5 | NestInterceptor, 6 | } from '@nestjs/common' 7 | import { Observable } from 'rxjs' 8 | import { map } from 'rxjs/operators' 9 | 10 | interface Response { 11 | data: T 12 | } 13 | 14 | @Injectable() 15 | export class TransformInterceptor 16 | implements NestInterceptor> { 17 | intercept( 18 | context: ExecutionContext, 19 | next: CallHandler, 20 | ): Observable> { 21 | return next.handle().pipe( 22 | map(rawData => { 23 | return { 24 | data: rawData, 25 | status: 0, 26 | message: '请求成功', 27 | } 28 | }), 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/core/middleware/logger.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '../../shared/utils/logger' 2 | 3 | export function logger(req, res, next) { 4 | const statusCode = res.statusCode 5 | const logFormat = `${req.method} ${req.originalUrl} ip: ${req.ip} statusCode: ${statusCode}` 6 | 7 | next() 8 | 9 | if (statusCode >= 500) { 10 | Logger.error(logFormat) 11 | } else if (statusCode >= 400) { 12 | Logger.warn(logFormat) 13 | } else { 14 | Logger.log(logFormat) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/core/pipe/validation.pipe.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArgumentMetadata, 3 | BadRequestException, 4 | Injectable, 5 | PipeTransform, 6 | } from '@nestjs/common' 7 | import { plainToClass } from 'class-transformer' 8 | import { validate } from 'class-validator' 9 | import * as _ from 'lodash' 10 | 11 | @Injectable() 12 | export class ValidationPipe implements PipeTransform { 13 | async transform(value, metadata: ArgumentMetadata) { 14 | const { metatype } = metadata 15 | if (!metatype || !this.toValidate(metatype)) { 16 | return value 17 | } 18 | const object = plainToClass(metatype, value) 19 | const errors = await validate(object) 20 | if (errors.length > 0) { 21 | const errorMessage = _.values(errors[0].constraints)[0] 22 | throw new BadRequestException(errorMessage) 23 | } 24 | return value 25 | } 26 | 27 | private toValidate(metatype): boolean { 28 | const types = [String, Boolean, Number, Array, Object] 29 | return !types.find(type => metatype === type) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/features/apis/account/account.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Post } from '@nestjs/common' 2 | import { ApiImplicitBody, ApiUseTags } from '@nestjs/swagger' 3 | 4 | import { AccountDto } from '../../dtos/account.dto' 5 | import { Token } from '../../interfaces/auth.interface' 6 | 7 | import { AccountService } from './account.service' 8 | 9 | @ApiUseTags('account') 10 | @Controller() 11 | export class AccountController { 12 | constructor(private readonly accountService: AccountService) {} 13 | 14 | // 注册 15 | @Post('signUp') 16 | async signUp(@Body() authDto: AccountDto): Promise { 17 | return await this.accountService.signUp(authDto) 18 | } 19 | 20 | // 登录 21 | @Post('signIn') 22 | async signIn(@Body() authDto: AccountDto): Promise { 23 | return await this.accountService.signIn(authDto) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/features/apis/account/account.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { JwtService } from '@nestjs/jwt' 3 | import { InjectRepository } from '@nestjs/typeorm' 4 | import { Repository } from 'typeorm' 5 | 6 | import { AccountDto } from '../../dtos/account.dto' 7 | import { UserEntity } from '../../entities/user.entity' 8 | import { Token } from '../../interfaces/auth.interface' 9 | import { AuthService } from '../auth/auth.service' 10 | 11 | @Injectable() 12 | export class AccountService { 13 | constructor( 14 | private readonly jwtService: JwtService, 15 | private readonly authService: AuthService, 16 | @InjectRepository(UserEntity) 17 | private readonly userRepository: Repository, 18 | ) {} 19 | async signIn(authDto: AccountDto): Promise { 20 | return this.authService.createToken(authDto.email) 21 | } 22 | 23 | async signUp(authDto: AccountDto): Promise { 24 | await this.userRepository.save(authDto) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/features/apis/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { JwtService } from '@nestjs/jwt' 3 | import { InjectRepository } from '@nestjs/typeorm' 4 | import { Repository } from 'typeorm' 5 | 6 | import config from '../../../config' 7 | import { UserEntity } from '../../entities/user.entity' 8 | import { Token } from '../../interfaces/auth.interface' 9 | 10 | @Injectable() 11 | export class AuthService { 12 | constructor( 13 | @InjectRepository(UserEntity) 14 | private readonly userRepository: Repository, 15 | private readonly jwtService: JwtService, 16 | ) {} 17 | 18 | createToken(email: string): Token { 19 | const accessToken = this.jwtService.sign({ email }) 20 | return { 21 | expires_in: config.jwt.signOptions.expiresIn, 22 | access_token: accessToken, 23 | } 24 | } 25 | 26 | async validateUser(payload: UserEntity): Promise { 27 | return await this.userRepository.find({ email: payload.email }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/features/apis/auth/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common' 2 | import { PassportStrategy } from '@nestjs/passport' 3 | import { ExtractJwt, Strategy } from 'passport-jwt' 4 | 5 | import config from '../../../config' 6 | import { UserEntity } from '../../entities/user.entity' 7 | 8 | import { AuthService } from './auth.service' 9 | 10 | @Injectable() 11 | export class JwtStrategy extends PassportStrategy(Strategy) { 12 | constructor(private readonly authService: AuthService) { 13 | super({ 14 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 15 | secretOrKey: config.jwt.secret, 16 | }) 17 | } 18 | 19 | async validate(payload: UserEntity) { 20 | const user = await this.authService.validateUser(payload) 21 | if (!user) { 22 | throw new UnauthorizedException('身份验证失败') 23 | } 24 | return user 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/features/apis/cats/cats.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | ClassSerializerInterceptor, 4 | Controller, 5 | Delete, 6 | Get, 7 | Param, 8 | Post, 9 | Render, 10 | UseGuards, 11 | UseInterceptors, 12 | } from '@nestjs/common' 13 | import { AuthGuard } from '@nestjs/passport' 14 | import { ApiBearerAuth, ApiUseTags } from '@nestjs/swagger' 15 | import { EntityManager, Transaction, TransactionManager } from 'typeorm' 16 | 17 | import { CreateCatDto } from '../../dtos/cat.dto' 18 | import { CatEntity } from '../../entities/cat.entity' 19 | 20 | import { CatsService } from './cats.service' 21 | 22 | @ApiUseTags('cats') 23 | @ApiBearerAuth() 24 | @Controller('cats') 25 | @UseGuards(AuthGuard()) 26 | export class CatsController { 27 | constructor(private readonly catsService: CatsService) {} 28 | 29 | @Get('page') 30 | @Render('catsPage') 31 | getCatsPage(): Promise { 32 | return this.catsService.getCats() 33 | } 34 | 35 | @Get(':id') 36 | @UseInterceptors(ClassSerializerInterceptor) 37 | findOne(@Param('id') id: string): Promise[]> { 38 | return this.catsService.getCat(id) 39 | } 40 | 41 | @Post() 42 | create(@Body() createCatDto: CreateCatDto): Promise { 43 | return this.catsService.createCat(createCatDto) 44 | } 45 | 46 | @Delete(':name') 47 | @Transaction() 48 | delete( 49 | @Param('name') name: string, 50 | @TransactionManager() manager: EntityManager, 51 | ): Promise { 52 | return this.catsService.deleteCat(name, manager) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/features/apis/cats/cats.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { InjectRepository } from '@nestjs/typeorm' 3 | import { EntityManager, Repository } from 'typeorm' 4 | 5 | import { LunarCalendarService } from '../../../shared/services/lunar-calendar/lunar-calendar.service' 6 | import { Logger } from '../../../shared/utils/logger' 7 | import { CreateCatDto } from '../../dtos/cat.dto' 8 | import { CatEntity } from '../../entities/cat.entity' 9 | 10 | @Injectable() 11 | export class CatsService { 12 | constructor( 13 | @InjectRepository(CatEntity) 14 | private readonly catRepository: Repository, 15 | private readonly lunarCalendarService: LunarCalendarService, 16 | ) {} 17 | 18 | async getCat(id: string): Promise[]> { 19 | Logger.info('id', id) 20 | const lunarCalendar = await this.lunarCalendarService 21 | .getLunarCalendar() 22 | .toPromise() 23 | Logger.log(lunarCalendar) 24 | return await this.catRepository.find({ id }) 25 | } 26 | 27 | async getCats(): Promise { 28 | const cats = await this.catRepository.find() 29 | return { 30 | cats, 31 | title: 'Cats List', 32 | } 33 | } 34 | 35 | async createCat(createCatDto: CreateCatDto): Promise { 36 | await this.catRepository.save(createCatDto) 37 | } 38 | 39 | async deleteCat(name: string, manager: EntityManager): Promise { 40 | await manager.delete(CatEntity, { name }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/features/apis/dogs/dogs.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClassSerializerInterceptor, 3 | Controller, 4 | UseInterceptors, 5 | } from '@nestjs/common' 6 | import { ApiBearerAuth, ApiUseTags } from '@nestjs/swagger' 7 | import { Crud, CrudController } from '@nestjsx/crud' 8 | 9 | import { DogEntity } from '../../entities/dog.entity' 10 | 11 | import { DogsService } from './dogs.service' 12 | 13 | @ApiUseTags('dogs') 14 | @ApiBearerAuth() 15 | @Crud({ 16 | model: { 17 | type: DogEntity, 18 | }, 19 | }) 20 | @UseInterceptors(ClassSerializerInterceptor) 21 | @Controller('dogs') 22 | export class DogsController implements CrudController { 23 | constructor(public service: DogsService) {} 24 | } 25 | -------------------------------------------------------------------------------- /src/features/apis/dogs/dogs.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { InjectRepository } from '@nestjs/typeorm' 3 | import { TypeOrmCrudService } from '@nestjsx/crud-typeorm' 4 | 5 | import { DogEntity } from '../../entities/dog.entity' 6 | 7 | @Injectable() 8 | export class DogsService extends TypeOrmCrudService { 9 | constructor(@InjectRepository(DogEntity) repo) { 10 | super(repo) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/features/dtos/account.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiModelProperty } from '@nestjs/swagger' 2 | import { IsEmail, IsString } from 'class-validator' 3 | 4 | export class AccountDto { 5 | @ApiModelProperty() 6 | @IsString() 7 | @IsEmail() 8 | readonly email: string 9 | 10 | @ApiModelProperty() 11 | @IsString() 12 | readonly password: string 13 | } 14 | -------------------------------------------------------------------------------- /src/features/dtos/cat.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiModelProperty } from '@nestjs/swagger' 2 | import { IsInt, IsString } from 'class-validator' 3 | 4 | export class CreateCatDto { 5 | @ApiModelProperty() 6 | @IsString() 7 | readonly name: string 8 | 9 | @ApiModelProperty() 10 | @IsInt() 11 | readonly age: number 12 | 13 | @ApiModelProperty() 14 | @IsString() 15 | readonly breed: string 16 | } 17 | -------------------------------------------------------------------------------- /src/features/entities/cat.entity.ts: -------------------------------------------------------------------------------- 1 | import { Transform } from 'class-transformer' 2 | import { Column, Entity } from 'typeorm' 3 | 4 | import { CommonEntity } from './common.entity' 5 | 6 | @Entity('cat') 7 | export class CatEntity extends CommonEntity { 8 | @Column({ length: 50 }) 9 | @Transform(value => `cat: ${value}`) 10 | name: string 11 | 12 | @Column() 13 | age: number 14 | 15 | @Column({ length: 100, nullable: true }) 16 | breed: string 17 | } 18 | -------------------------------------------------------------------------------- /src/features/entities/common.entity.ts: -------------------------------------------------------------------------------- 1 | import { Exclude } from 'class-transformer' 2 | import { 3 | CreateDateColumn, 4 | PrimaryGeneratedColumn, 5 | UpdateDateColumn, 6 | } from 'typeorm' 7 | 8 | export class CommonEntity { 9 | @PrimaryGeneratedColumn('uuid') 10 | id: string 11 | 12 | @Exclude() 13 | @CreateDateColumn({ 14 | comment: '创建时间', 15 | }) 16 | create_at: number 17 | 18 | @Exclude() 19 | @UpdateDateColumn({ 20 | comment: '更新时间', 21 | }) 22 | update_at: number 23 | } 24 | -------------------------------------------------------------------------------- /src/features/entities/dog.entity.ts: -------------------------------------------------------------------------------- 1 | import { Expose, Transform } from 'class-transformer' 2 | import { Column, Entity } from 'typeorm' 3 | 4 | import { CommonEntity } from './common.entity' 5 | 6 | @Entity('dog') 7 | export class DogEntity extends CommonEntity { 8 | @Column({ length: 50 }) 9 | @Transform(value => `dog: ${value}`) 10 | name: string 11 | 12 | @Column() 13 | age: number 14 | 15 | @Column({ length: 100, nullable: true }) 16 | breed: string 17 | 18 | @Expose() 19 | get isOld(): boolean { 20 | return this.age > 10 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/features/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import * as bcrypt from 'bcryptjs' 2 | import { Exclude } from 'class-transformer' 3 | import { IsEmail } from 'class-validator' 4 | import { BeforeInsert, Column, Entity } from 'typeorm' 5 | 6 | import { CommonEntity } from './common.entity' 7 | 8 | @Entity('user') 9 | export class UserEntity extends CommonEntity { 10 | @Column({ 11 | comment: '邮箱', 12 | unique: true, 13 | }) 14 | @IsEmail() 15 | email: string 16 | 17 | @Exclude({ toPlainOnly: true }) 18 | @Column({ comment: '密码' }) 19 | password: string 20 | 21 | @BeforeInsert() 22 | async beforeInsert() { 23 | const salt = await bcrypt.genSalt(10) 24 | this.password = await bcrypt.hash(this.password || '12345678', salt) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/features/features.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { JwtModule } from '@nestjs/jwt' 3 | import { PassportModule } from '@nestjs/passport' 4 | import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm' 5 | 6 | import config from '../config' 7 | 8 | import { AccountController } from './apis/account/account.controller' 9 | import { AccountService } from './apis/account/account.service' 10 | import { AuthService } from './apis/auth/auth.service' 11 | import { JwtStrategy } from './apis/auth/jwt.strategy' 12 | import { CatsController } from './apis/cats/cats.controller' 13 | import { CatsService } from './apis/cats/cats.service' 14 | import { DogsController } from './apis/dogs/dogs.controller' 15 | import { DogsService } from './apis/dogs/dogs.service' 16 | import { CatEntity } from './entities/cat.entity' 17 | import { DogEntity } from './entities/dog.entity' 18 | import { UserEntity } from './entities/user.entity' 19 | 20 | const ENTITIES = [CatEntity, DogEntity, UserEntity] 21 | 22 | @Module({ 23 | imports: [ 24 | TypeOrmModule.forRoot(config.orm as TypeOrmModuleOptions), 25 | TypeOrmModule.forFeature([...ENTITIES]), 26 | PassportModule.register({ defaultStrategy: 'jwt' }), 27 | JwtModule.register(config.jwt), 28 | ], 29 | controllers: [CatsController, DogsController, AccountController], 30 | providers: [ 31 | CatsService, 32 | DogsService, 33 | AuthService, 34 | JwtStrategy, 35 | AccountService, 36 | ], 37 | exports: [], 38 | }) 39 | export class FeaturesModule {} 40 | -------------------------------------------------------------------------------- /src/features/interfaces/auth.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Token { 2 | access_token: string 3 | expires_in: number 4 | } 5 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core' 2 | import { NestExpressApplication } from '@nestjs/platform-express' 3 | import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger' 4 | import * as rateLimit from 'express-rate-limit' 5 | import * as helmet from 'helmet' 6 | import { join } from 'path' 7 | 8 | import { AppModule } from './app.module' 9 | import config from './config' 10 | import { ExceptionsFilter } from './core/filter/errors.filter' 11 | import { TransformInterceptor } from './core/interceptor/transform.interceptor' 12 | import { logger } from './core/middleware/logger.middleware' 13 | import { ValidationPipe } from './core/pipe/validation.pipe' 14 | import { Logger } from './shared/utils/logger' 15 | 16 | const API_PREFIX = 'api/v1' 17 | 18 | async function initSwagger(app) { 19 | const options = new DocumentBuilder() 20 | .setTitle('Awesome-nest') 21 | .setDescription('The Awesome-nest API Documents') 22 | .setBasePath(API_PREFIX) 23 | .addBearerAuth() 24 | .setVersion('0.0.1') 25 | .build() 26 | 27 | const document = SwaggerModule.createDocument(app, options) 28 | SwaggerModule.setup('docs', app, document) 29 | // swagger 地址: http://${config.hostName}:${config.port}/docs 30 | } 31 | 32 | async function bootstrap() { 33 | const app = await NestFactory.create(AppModule, { 34 | cors: true, 35 | }) 36 | 37 | app.setGlobalPrefix(API_PREFIX) 38 | 39 | await initSwagger(app) 40 | 41 | app.useStaticAssets(join(__dirname, '..', 'static')) 42 | app.setBaseViewsDir(join(__dirname, '..', 'views')) 43 | app.setViewEngine('hbs') 44 | 45 | app.use(helmet()) 46 | app.use( 47 | rateLimit({ 48 | windowMs: 15 * 60 * 1000, // 15 minutes 49 | max: 100, // limit each IP to 100 requests per windowMs 50 | }), 51 | ) 52 | app.use(logger) 53 | app.useGlobalFilters(new ExceptionsFilter()) 54 | app.useGlobalInterceptors(new TransformInterceptor()) 55 | app.useGlobalPipes(new ValidationPipe()) 56 | 57 | await app.listen(config.port, config.hostName, () => { 58 | Logger.log( 59 | `Awesome-nest API server has been started on http://${config.hostName}:${config.port}`, 60 | ) 61 | }) 62 | } 63 | 64 | bootstrap() 65 | -------------------------------------------------------------------------------- /src/shared/services/lunar-calendar/lunar-calendar.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpService, Injectable } from '@nestjs/common' 2 | import { of, Observable } from 'rxjs' 3 | import { catchError, map, timeout } from 'rxjs/operators' 4 | 5 | @Injectable() 6 | export class LunarCalendarService { 7 | constructor(private readonly httpService: HttpService) {} 8 | 9 | getLunarCalendar(): Observable { 10 | return this.httpService 11 | .get('https://www.sojson.com/open/api/lunar/json.shtml') 12 | .pipe( 13 | map(res => res.data.data), 14 | timeout(5000), 15 | catchError(error => of(`Bad Promise: ${error}`)), 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, HttpModule, Module } from '@nestjs/common' 2 | 3 | import { LunarCalendarService } from './services/lunar-calendar/lunar-calendar.service' 4 | 5 | @Global() 6 | @Module({ 7 | imports: [HttpModule], 8 | providers: [LunarCalendarService], 9 | exports: [HttpModule, LunarCalendarService], 10 | }) 11 | export class SharedModule {} 12 | -------------------------------------------------------------------------------- /src/shared/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import Chalk from 'chalk' 2 | import * as _ from 'lodash' 3 | import * as Log4js from 'log4js' 4 | import * as Moment from 'moment' 5 | import * as Path from 'path' 6 | import * as StackTrace from 'stacktrace-js' 7 | import * as Util from 'util' 8 | 9 | import { isProd } from '../../config' 10 | 11 | export enum LoggerLevel { 12 | ALL = 'ALL', 13 | MARK = 'MARK', 14 | TRACE = 'TRACE', 15 | DEBUG = 'DEBUG', 16 | INFO = 'INFO', 17 | WARN = 'WARN', 18 | ERROR = 'ERROR', 19 | FATAL = 'FATAL', 20 | OFF = 'OFF', 21 | } 22 | 23 | export class ContextTrace { 24 | constructor( 25 | public readonly context: string, 26 | public readonly path?: string, 27 | public readonly lineNumber?: number, 28 | public readonly columnNumber?: number, 29 | ) {} 30 | } 31 | 32 | Log4js.addLayout('Awesome-nest', (logConfig: any) => { 33 | return (logEvent: Log4js.LoggingEvent): string => { 34 | let moduleName = '' 35 | let position = '' 36 | 37 | const messageList: string[] = [] 38 | logEvent.data.forEach((value: any) => { 39 | if (value instanceof ContextTrace) { 40 | moduleName = value.context 41 | if (value.lineNumber && value.columnNumber) { 42 | position = `${value.lineNumber}, ${value.columnNumber}` 43 | } 44 | return 45 | } 46 | 47 | if (typeof value !== 'string') { 48 | value = Util.inspect(value, false, 3, true) 49 | } 50 | 51 | messageList.push(value) 52 | }) 53 | 54 | const messageOutput: string = messageList.join(' ') 55 | const positionOutput: string = position ? ` [${position}]` : '' 56 | const typeOutput = `[${logConfig.type}] ${logEvent.pid.toString()} - ` 57 | const dateOutput = `${Moment(logEvent.startTime).format( 58 | 'YYYY-MM-DD HH:mm:ss', 59 | )}` 60 | const moduleOutput: string = moduleName 61 | ? `[${moduleName}] ` 62 | : '[LoggerService] ' 63 | let levelOutput = `[${logEvent.level}] ${messageOutput}` 64 | 65 | switch (logEvent.level.toString()) { 66 | case LoggerLevel.DEBUG: 67 | levelOutput = Chalk.green(levelOutput) 68 | break 69 | case LoggerLevel.INFO: 70 | levelOutput = Chalk.cyan(levelOutput) 71 | break 72 | case LoggerLevel.WARN: 73 | levelOutput = Chalk.yellow(levelOutput) 74 | break 75 | case LoggerLevel.ERROR: 76 | levelOutput = Chalk.red(levelOutput) 77 | break 78 | case LoggerLevel.FATAL: 79 | levelOutput = Chalk.hex('#DD4C35')(levelOutput) 80 | break 81 | default: 82 | levelOutput = Chalk.grey(levelOutput) 83 | break 84 | } 85 | 86 | return `${Chalk.green(typeOutput)}${dateOutput} ${Chalk.yellow( 87 | moduleOutput, 88 | )}${levelOutput}${positionOutput}` 89 | } 90 | }) 91 | 92 | if (isProd) { 93 | Log4js.configure({ 94 | appenders: { 95 | fileAppender: { 96 | type: 'DateFile', 97 | filename: './logs/prod.log', 98 | pattern: '-yyyy-MM-dd.log', 99 | alwaysIncludePattern: true, 100 | layout: { type: 'Awesome-nest' }, 101 | daysToKeep: 60, 102 | }, 103 | console: { 104 | type: 'stdout', 105 | layout: { type: 'Awesome-nest' }, 106 | level: 'info', 107 | }, 108 | logLevelFilterAppender: { 109 | type: 'logLevelFilter', 110 | appender: 'fileAppender', 111 | level: 'warn', 112 | }, 113 | }, 114 | pm2: true, 115 | disableClustering: true, 116 | categories: { 117 | default: { 118 | appenders: ['logLevelFilterAppender', 'console'], 119 | level: 'info', 120 | }, 121 | }, 122 | }) 123 | } else { 124 | Log4js.configure({ 125 | appenders: { 126 | console: { 127 | type: 'stdout', 128 | layout: { type: 'Awesome-nest' }, 129 | }, 130 | }, 131 | categories: { 132 | default: { 133 | appenders: ['console'], 134 | level: 'debug', 135 | }, 136 | }, 137 | }) 138 | } 139 | 140 | const logger = Log4js.getLogger() 141 | 142 | export class Logger { 143 | static trace(...args) { 144 | logger.trace(Logger.getStackTrace(), ...args) 145 | } 146 | 147 | static debug(...args) { 148 | logger.debug(Logger.getStackTrace(), ...args) 149 | } 150 | 151 | static log(...args) { 152 | logger.info(Logger.getStackTrace(), ...args) 153 | } 154 | 155 | static info(...args) { 156 | logger.info(Logger.getStackTrace(), ...args) 157 | } 158 | 159 | static warn(...args) { 160 | logger.warn(Logger.getStackTrace(), ...args) 161 | } 162 | 163 | static warning(...args) { 164 | logger.warn(Logger.getStackTrace(), ...args) 165 | } 166 | 167 | static error(...args) { 168 | logger.error(Logger.getStackTrace(), ...args) 169 | } 170 | 171 | static fatal(...args) { 172 | logger.fatal(Logger.getStackTrace(), ...args) 173 | } 174 | 175 | static getStackTrace(deep: number = 2): ContextTrace { 176 | const stackList: StackTrace.StackFrame[] = StackTrace.getSync() 177 | const stackInfo: StackTrace.StackFrame = stackList[deep] 178 | 179 | const lineNumber: number = stackInfo.lineNumber 180 | const columnNumber: number = stackInfo.columnNumber 181 | const fileName: string = stackInfo.fileName 182 | 183 | const extnameLength: number = Path.extname(fileName).length 184 | let basename: string = Path.basename(fileName) 185 | basename = basename.substr(0, basename.length - extnameLength) 186 | const context: string = _.upperFirst(_.camelCase(basename)) 187 | 188 | return new ContextTrace(context, fileName, lineNumber, columnNumber) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tc9011/awesome-nest/397636309bbbfac98ec5972c88e6abf6680fdc78/static/.gitkeep -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | import * as request from 'supertest' 3 | import { AppModule } from './../src/app.module' 4 | 5 | describe('AppController (e2e)', () => { 6 | let app 7 | 8 | beforeEach(async () => { 9 | const moduleFixture: TestingModule = await Test.createTestingModule({ 10 | imports: [AppModule], 11 | }).compile() 12 | 13 | app = moduleFixture.createNestApplication() 14 | await app.init() 15 | }) 16 | 17 | it('/ (GET)', () => { 18 | return request(app.getHttpServer()) 19 | .get('/') 20 | .expect(200) 21 | .expect('Hello World!') 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /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", "migrations"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "es6", 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "baseUrl": "./", 12 | "incremental": true 13 | }, 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended", "tslint-config-prettier", "@leo-tools/tslint-config-common"], 4 | "rulesDirectory": [] 5 | } 6 | -------------------------------------------------------------------------------- /views/catsPage.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 20 | 21 | 22 |

{{ data.title }}

23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{#each data.cats}} 34 | 35 | 36 | 37 | 38 | 39 | 40 | {{/each}} 41 | 42 |
idnameagebreed
{{id}}{{name}}{{age}}{{breed}}
43 | 44 | 45 | --------------------------------------------------------------------------------