├── .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 |
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 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
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 | id |
27 | name |
28 | age |
29 | breed |
30 |
31 |
32 |
33 | {{#each data.cats}}
34 |
35 | {{id}} |
36 | {{name}} |
37 | {{age}} |
38 | {{breed}} |
39 |
40 | {{/each}}
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------