init
This commit is contained in:
commit
313b997903
26
.eslintignore
Normal file
26
.eslintignore
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
dist
|
||||||
|
back
|
||||||
|
node_modules
|
||||||
|
pnpm-lock.yaml
|
||||||
|
docker
|
||||||
|
Dockerfile*
|
||||||
|
LICENSE
|
||||||
|
yarn-error.log
|
||||||
|
.history
|
||||||
|
.vscode
|
||||||
|
.docusaurus
|
||||||
|
.dockerignore
|
||||||
|
.DS_Store
|
||||||
|
.eslintignore
|
||||||
|
.editorconfig
|
||||||
|
.gitignore
|
||||||
|
.prettierignore
|
||||||
|
.eslintcache
|
||||||
|
*.lock
|
||||||
|
**/*.svg
|
||||||
|
**/*.md
|
||||||
|
**/*.svg
|
||||||
|
**/*.ejs
|
||||||
|
**/*.html
|
||||||
|
**/*.png
|
||||||
|
**/*.toml
|
138
.eslintrc.js
Normal file
138
.eslintrc.js
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
// .eslintrc.js
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: 'tsconfig.json',
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint', 'jest', 'prettier', 'import', 'unused-imports'],
|
||||||
|
extends: [
|
||||||
|
// airbnb规范
|
||||||
|
// https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb
|
||||||
|
'airbnb-base',
|
||||||
|
// 兼容typescript的airbnb规范
|
||||||
|
// https://github.com/iamturns/eslint-config-airbnb-typescript
|
||||||
|
'airbnb-typescript/base',
|
||||||
|
|
||||||
|
// typescript的eslint插件
|
||||||
|
// https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/README.md
|
||||||
|
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||||
|
|
||||||
|
// 支持jest
|
||||||
|
'plugin:jest/recommended',
|
||||||
|
// 使用prettier格式化代码
|
||||||
|
// https://github.com/prettier/eslint-config-prettier#readme
|
||||||
|
'prettier',
|
||||||
|
// 整合typescript-eslint与prettier
|
||||||
|
// https://github.com/prettier/eslint-plugin-prettier
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
/* ********************************** ES6+ ********************************** */
|
||||||
|
'no-console': 0,
|
||||||
|
'no-var-requires': 0,
|
||||||
|
'no-restricted-syntax': 0,
|
||||||
|
'no-continue': 0,
|
||||||
|
'no-await-in-loop': 0,
|
||||||
|
'no-return-await': 0,
|
||||||
|
'no-unused-vars': 0,
|
||||||
|
'no-multi-assign': 0,
|
||||||
|
'no-param-reassign': [2, { props: false }],
|
||||||
|
'import/prefer-default-export': 0,
|
||||||
|
'import/no-cycle': 0,
|
||||||
|
'import/no-dynamic-require': 0,
|
||||||
|
'max-classes-per-file': 0,
|
||||||
|
'class-methods-use-this': 0,
|
||||||
|
'guard-for-in': 0,
|
||||||
|
'no-underscore-dangle': 0,
|
||||||
|
'no-plusplus': 0,
|
||||||
|
'no-lonely-if': 0,
|
||||||
|
'no-bitwise': ['error', { allow: ['~'] }],
|
||||||
|
|
||||||
|
/* ********************************** Module Import ********************************** */
|
||||||
|
|
||||||
|
'import/no-absolute-path': 0,
|
||||||
|
'import/extensions': 0,
|
||||||
|
'import/no-named-default': 0,
|
||||||
|
'no-restricted-exports': 0,
|
||||||
|
|
||||||
|
// 一部分文件在导入devDependencies的依赖时不报错
|
||||||
|
'import/no-extraneous-dependencies': [
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
devDependencies: [
|
||||||
|
'**/*.test.{ts,js}',
|
||||||
|
'**/*.spec.{ts,js}',
|
||||||
|
'./test/**.{ts,js}',
|
||||||
|
'./scripts/**/*.{ts,js}',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 模块导入顺序规则
|
||||||
|
'import/order': [
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
pathGroups: [
|
||||||
|
{
|
||||||
|
pattern: '@/**',
|
||||||
|
group: 'external',
|
||||||
|
position: 'after',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
alphabetize: { order: 'asc', caseInsensitive: false },
|
||||||
|
'newlines-between': 'always-and-inside-groups',
|
||||||
|
warnOnUnassignedImports: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 自动删除未使用的导入
|
||||||
|
// https://github.com/sweepline/eslint-plugin-unused-imports
|
||||||
|
'unused-imports/no-unused-imports': 1,
|
||||||
|
'unused-imports/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
vars: 'all',
|
||||||
|
args: 'none',
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
/* ********************************** Typescript ********************************** */
|
||||||
|
'@typescript-eslint/no-unused-vars': 0,
|
||||||
|
'@typescript-eslint/no-empty-interface': 0,
|
||||||
|
'@typescript-eslint/no-this-alias': 0,
|
||||||
|
'@typescript-eslint/no-var-requires': 0,
|
||||||
|
'@typescript-eslint/no-use-before-define': 0,
|
||||||
|
'@typescript-eslint/explicit-member-accessibility': 0,
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 0,
|
||||||
|
'@typescript-eslint/no-unnecessary-type-assertion': 0,
|
||||||
|
'@typescript-eslint/require-await': 0,
|
||||||
|
'@typescript-eslint/no-for-in-array': 0,
|
||||||
|
'@typescript-eslint/interface-name-prefix': 0,
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 0,
|
||||||
|
'@typescript-eslint/no-explicit-any': 0,
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 0,
|
||||||
|
'@typescript-eslint/no-floating-promises': 0,
|
||||||
|
'@typescript-eslint/restrict-template-expressions': 0,
|
||||||
|
'@typescript-eslint/no-unsafe-assignment': 0,
|
||||||
|
'@typescript-eslint/no-unsafe-return': 0,
|
||||||
|
'@typescript-eslint/no-unused-expressions': 0,
|
||||||
|
'@typescript-eslint/no-misused-promises': 0,
|
||||||
|
'@typescript-eslint/no-unsafe-member-access': 0,
|
||||||
|
'@typescript-eslint/no-unsafe-call': 0,
|
||||||
|
'@typescript-eslint/no-unsafe-argument': 0,
|
||||||
|
'@typescript-eslint/ban-ts-comment': 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
extensions: ['.ts', '.d.ts', '.cts', '.mts', '.js', '.cjs', 'mjs', '.json'],
|
||||||
|
},
|
||||||
|
};
|
56
.gitignore
vendored
Normal file
56
.gitignore
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
/coverage
|
||||||
|
/.nyc_output
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# temp directory
|
||||||
|
.temp
|
||||||
|
.tmp
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
26
.prettierignore
Normal file
26
.prettierignore
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
dist
|
||||||
|
back
|
||||||
|
node_modules
|
||||||
|
pnpm-lock.yaml
|
||||||
|
docker
|
||||||
|
Dockerfile*
|
||||||
|
LICENSE
|
||||||
|
yarn-error.log
|
||||||
|
.history
|
||||||
|
.vscode
|
||||||
|
.docusaurus
|
||||||
|
.dockerignore
|
||||||
|
.DS_Store
|
||||||
|
.eslintignore
|
||||||
|
.editorconfig
|
||||||
|
.gitignore
|
||||||
|
.prettierignore
|
||||||
|
.eslintcache
|
||||||
|
*.lock
|
||||||
|
**/*.svg
|
||||||
|
**/*.md
|
||||||
|
**/*.svg
|
||||||
|
**/*.ejs
|
||||||
|
**/*.html
|
||||||
|
**/*.png
|
||||||
|
**/*.toml
|
19
.prettierrc.js
Normal file
19
.prettierrc.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// .prettierrc.js
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"printWidth": 100,
|
||||||
|
"proseWrap": "never",
|
||||||
|
"endOfLine": "auto",
|
||||||
|
"semi": true,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ".prettierrc",
|
||||||
|
"options": {
|
||||||
|
"parser": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
20
.vscode/launch.json
vendored
Normal file
20
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "debug 3rapp",
|
||||||
|
"request": "launch",
|
||||||
|
"runtimeArgs": ["run-script", "start:debug"],
|
||||||
|
"autoAttachChildProcesses": true,
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"runtimeExecutable": "pnpm",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"type": "node",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
17
.vscode/settings.json
vendored
Normal file
17
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
// 使用eslint+prettier自动格式化
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": true
|
||||||
|
},
|
||||||
|
// 导入模块时默认使用相对于项目的路径
|
||||||
|
"javascript.preferences.importModuleSpecifier": "project-relative",
|
||||||
|
// jsdoc注释时不带return
|
||||||
|
"typescript.suggest.jsdoc.generateReturns": false,
|
||||||
|
// 只启用项目范围的错误报告
|
||||||
|
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
|
||||||
|
// 使用项目内部安装的ts的sdk
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
// 默认使用pnpm作为包安装的管理工具
|
||||||
|
"npm.packageManager": "pnpm",
|
||||||
|
}
|
73
README.md
Normal file
73
README.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||||
|
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||||
|
|
||||||
|
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
||||||
|
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
||||||
|
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
||||||
|
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||||
|
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||||
|
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
||||||
|
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||||
|
</p>
|
||||||
|
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
|
||||||
|
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the app
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# development
|
||||||
|
$ pnpm run start
|
||||||
|
|
||||||
|
# watch mode
|
||||||
|
$ pnpm run start:dev
|
||||||
|
|
||||||
|
# production mode
|
||||||
|
$ pnpm run start:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# unit tests
|
||||||
|
$ pnpm run test
|
||||||
|
|
||||||
|
# e2e tests
|
||||||
|
$ pnpm run test:e2e
|
||||||
|
|
||||||
|
# test coverage
|
||||||
|
$ pnpm run test:cov
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
## Stay in touch
|
||||||
|
|
||||||
|
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
|
||||||
|
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||||
|
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Nest is [MIT licensed](LICENSE).
|
BIN
database.db
Normal file
BIN
database.db
Normal file
Binary file not shown.
10
nest-cli.json
Normal file
10
nest-cli.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"deleteOutDir": true,
|
||||||
|
"builder": "swc",
|
||||||
|
"typeCheck": true
|
||||||
|
}
|
||||||
|
}
|
88
package.json
Normal file
88
package.json
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
{
|
||||||
|
"name": "nestapp",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"author": "",
|
||||||
|
"private": true,
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nest build",
|
||||||
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
|
"start": "nest start",
|
||||||
|
"start:dev": "nest start --watch",
|
||||||
|
"start:debug": "nest start --debug --watch",
|
||||||
|
"start:prod": "node dist/main",
|
||||||
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"test:cov": "jest --coverage",
|
||||||
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nestjs/common": "^10.3.4",
|
||||||
|
"@nestjs/core": "^10.3.4",
|
||||||
|
"@nestjs/platform-fastify": "^10.3.7",
|
||||||
|
"@nestjs/swagger": "^7.3.1",
|
||||||
|
"@nestjs/typeorm": "^10.0.2",
|
||||||
|
"better-sqlite3": "^9.4.3",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.1",
|
||||||
|
"deepmerge": "^4.3.1",
|
||||||
|
"fastify": "^4.26.2",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"reflect-metadata": "^0.2.1",
|
||||||
|
"rxjs": "^7.8.1",
|
||||||
|
"sanitize-html": "^2.13.0",
|
||||||
|
"typeorm": "^0.3.20"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nestjs/cli": "^10.3.2",
|
||||||
|
"@nestjs/schematics": "^10.1.1",
|
||||||
|
"@nestjs/testing": "^10.3.4",
|
||||||
|
"@swc/cli": "^0.3.10",
|
||||||
|
"@swc/core": "^1.4.8",
|
||||||
|
"@types/jest": "^29.5.12",
|
||||||
|
"@types/jquery": "^3.5.29",
|
||||||
|
"@types/lodash": "^4.17.0",
|
||||||
|
"@types/node": "^20.11.30",
|
||||||
|
"@types/sanitize-html": "^2.11.0",
|
||||||
|
"@types/supertest": "^6.0.2",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
||||||
|
"@typescript-eslint/parser": "^7.3.1",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
|
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-import": "^2.29.1",
|
||||||
|
"eslint-plugin-jest": "^27.9.0",
|
||||||
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
|
"eslint-plugin-unused-imports": "^3.1.0",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
|
"supertest": "^6.3.4",
|
||||||
|
"ts-jest": "^29.1.2",
|
||||||
|
"ts-loader": "^9.5.1",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"typescript": "^5.4.2"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"rootDir": "src",
|
||||||
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
},
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"**/*.(t|j)s"
|
||||||
|
],
|
||||||
|
"coverageDirectory": "../coverage",
|
||||||
|
"testEnvironment": "node"
|
||||||
|
}
|
||||||
|
}
|
7120
pnpm-lock.yaml
Normal file
7120
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
23
src/app.controller.spec.ts
Normal file
23
src/app.controller.spec.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
import { AppController } from './app.controller';
|
||||||
|
import { AppService } from './app.service';
|
||||||
|
|
||||||
|
describe('AppController', () => {
|
||||||
|
let appController: AppController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const app: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [AppController],
|
||||||
|
providers: [AppService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
appController = app.get<AppController>(AppController);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('root', () => {
|
||||||
|
it('should return "Hello World!"', () => {
|
||||||
|
expect(appController.getHello()).toBe('Hello World!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
13
src/app.controller.ts
Normal file
13
src/app.controller.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { AppService } from './app.service';
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
export class AppController {
|
||||||
|
constructor(private readonly appService: AppService) { }
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
getHello(): string {
|
||||||
|
return this.appService.getHello();
|
||||||
|
}
|
||||||
|
}
|
15
src/app.module.ts
Normal file
15
src/app.module.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { AppController } from './app.controller';
|
||||||
|
import { AppService } from './app.service';
|
||||||
|
import { ContentModule } from './modules/content/content.module';
|
||||||
|
import { CoreModule } from './modules/core/core.module';
|
||||||
|
import { DatabaseModule } from './modules/database/database.module';
|
||||||
|
import { database } from './config';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [ContentModule, CoreModule.forRoot(), DatabaseModule.forRoot(database)],
|
||||||
|
controllers: [AppController],
|
||||||
|
providers: [AppService],
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
9
src/app.service.ts
Normal file
9
src/app.service.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AppService {
|
||||||
|
getHello(): string {
|
||||||
|
console.log('hello leon zeng!');
|
||||||
|
return 'Hello World!';
|
||||||
|
}
|
||||||
|
}
|
8
src/config/database.config.ts
Normal file
8
src/config/database.config.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
export const database = (): TypeOrmModuleOptions => ({
|
||||||
|
type: 'better-sqlite3',
|
||||||
|
database: resolve(__dirname, '../../database.db'),
|
||||||
|
synchronize: true,
|
||||||
|
autoLoadEntities: true,
|
||||||
|
})
|
1
src/config/index.ts
Normal file
1
src/config/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './database.config';
|
9
src/example/demo1.ts
Normal file
9
src/example/demo1.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
function sum(a: number, b: number): number {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
interface SumFunction extends Function {
|
||||||
|
(a: number, b: number): number;
|
||||||
|
}
|
||||||
|
const mySum = (a: number, b?: number, c?: number): number => {
|
||||||
|
return a + b + c;
|
||||||
|
};
|
40
src/example/exp1.ts
Normal file
40
src/example/exp1.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
type DecoratorFunc = (target: any, key: string, descriptor: PropertyDescriptor) => void;
|
||||||
|
|
||||||
|
const createDecorator = (decorator: DecoratorFunc) => (Model: any, key: string) => {
|
||||||
|
const target = Model.prototype;
|
||||||
|
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
||||||
|
console.log(descriptor);
|
||||||
|
decorator(target, key, descriptor);
|
||||||
|
};
|
||||||
|
const logger: DecoratorFunc = (target, key, descriptor) => {
|
||||||
|
Object.defineProperty(target, key, {
|
||||||
|
...descriptor,
|
||||||
|
value: async (...args: any[]) => {
|
||||||
|
try {
|
||||||
|
console.log(descriptor, this, args);
|
||||||
|
return descriptor.value.apply(this, args);
|
||||||
|
} finally {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
console.log(`lasted logged in ${now.toString()}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
class User {
|
||||||
|
async login() {
|
||||||
|
console.log('login start');
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 10000);
|
||||||
|
console.log('login ................');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const exp1 = () => {
|
||||||
|
console.log('exp1');
|
||||||
|
const loggerDecorator = createDecorator(logger);
|
||||||
|
loggerDecorator(User, 'login');
|
||||||
|
const user = new User();
|
||||||
|
user.login();
|
||||||
|
console.log('user logged', user);
|
||||||
|
};
|
22
src/example/exp2.ts
Normal file
22
src/example/exp2.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
const HelloDerorator = <T extends new (...args: any[]) => any>(constructor: T) => {
|
||||||
|
return class extends constructor {
|
||||||
|
newProperty = 'new property';
|
||||||
|
hello = 'override';
|
||||||
|
sayHello() {
|
||||||
|
return this.hello;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@HelloDerorator
|
||||||
|
export class Hello {
|
||||||
|
[key: string]: any;
|
||||||
|
hello: string;
|
||||||
|
constructor() {
|
||||||
|
this.hello = 'hello';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const exp2 = () => {
|
||||||
|
const hello = new Hello();
|
||||||
|
console.log(hello.sayHello());
|
||||||
|
};
|
22
src/example/exp3.ts
Normal file
22
src/example/exp3.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
const SetNameDecorator = (firstName: string, secondName: string) => {
|
||||||
|
const name = `${firstName} ${secondName}`;
|
||||||
|
return <T extends new (...args: any[]) => any>(target: T) => {
|
||||||
|
return class extends target {
|
||||||
|
_name: string = name;
|
||||||
|
getMyname() {
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SetNameDecorator('zeng', 'leon')
|
||||||
|
class UserService {
|
||||||
|
[key: string]: any;
|
||||||
|
c() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const exp3 = () => {
|
||||||
|
const userService = new UserService();
|
||||||
|
console.log(userService.getMyname());
|
||||||
|
}
|
33
src/example/exp4.ts
Normal file
33
src/example/exp4.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
type UserProfile = Record<string, any> & {
|
||||||
|
phone?: number;
|
||||||
|
address?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProfileDecorator = (profile: UserProfile) => (target: any) => {
|
||||||
|
const Original = target;
|
||||||
|
let userinfo = '';
|
||||||
|
Object.keys(profile).forEach(key => {
|
||||||
|
userinfo += `${key}: ${profile[key]}\n`;
|
||||||
|
})
|
||||||
|
|
||||||
|
Original.prototype.userinfo = userinfo;
|
||||||
|
function constructor(...args: any[]) {
|
||||||
|
console.log('construct has been called');
|
||||||
|
return new Original(...args);
|
||||||
|
}
|
||||||
|
constructor.prototype = Original.prototype;
|
||||||
|
constructor.myinfo = `myinfo ${userinfo}`
|
||||||
|
return constructor as typeof Original;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ProfileDecorator({
|
||||||
|
phone: 1234567890,
|
||||||
|
address: 'zhongguo'
|
||||||
|
})
|
||||||
|
class User { }
|
||||||
|
|
||||||
|
export const exp4 = () => {
|
||||||
|
console.log("------------exp4-----------")
|
||||||
|
const user = new User();
|
||||||
|
console.log((user as any).userinfo);
|
||||||
|
}
|
35
src/example/exp5.ts
Normal file
35
src/example/exp5.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
|
||||||
|
const RoleDecorator = (roles: string[]) => (target: any, key: string) => {
|
||||||
|
if (!target.userRoles) {
|
||||||
|
target.userRoles = [];
|
||||||
|
}
|
||||||
|
console.log("测试编译", key, roles);
|
||||||
|
roles.forEach((role: string) => target.userRoles.push(role));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const SetRoleDecorator = <T extends new (...args: any[]) => any>(constructor: T) => {
|
||||||
|
const roles = [
|
||||||
|
{ name: 'super-admin', desc: '超级管理员' },
|
||||||
|
{ name: 'admin', desc: '管理员' },
|
||||||
|
{ name: 'user', desc: '普通用户' },
|
||||||
|
]
|
||||||
|
return class extends constructor {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
super(...args);
|
||||||
|
this.roles = roles.filter((role) => this.userRoles.includes(role.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SetRoleDecorator
|
||||||
|
class UserEntity {
|
||||||
|
@RoleDecorator(['admin', 'user'])
|
||||||
|
roles: string[] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const exp5 = () => {
|
||||||
|
const user = new UserEntity();
|
||||||
|
console.log(user.roles);
|
||||||
|
}
|
30
src/example/exp6.ts
Normal file
30
src/example/exp6.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
const loggerDecorator = () => {
|
||||||
|
return function logMethod(target: any, propertyName: string, propertyDescriptor: PropertyDescriptor) {
|
||||||
|
const method = propertyDescriptor.value;
|
||||||
|
|
||||||
|
propertyDescriptor.value = async function (...args: any[]) {
|
||||||
|
try {
|
||||||
|
return method.call(target, ...args);
|
||||||
|
} finally {
|
||||||
|
const now = Date.now();
|
||||||
|
console.log(`lasted logged in ${now.toString()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return propertyDescriptor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class UserService {
|
||||||
|
@loggerDecorator()
|
||||||
|
async login() {
|
||||||
|
console.log('login success');
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const exp6 = () => {
|
||||||
|
|
||||||
|
const user = new UserService();
|
||||||
|
user.login();
|
||||||
|
};
|
57
src/example/exp7.ts
Normal file
57
src/example/exp7.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
export const exp7 = () => {
|
||||||
|
|
||||||
|
const userService = new UserService();
|
||||||
|
userService.delete(1);
|
||||||
|
console.log(userService.getUsers());
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
type NewType = {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
const parseConf: ((...args: any[]) => any)[] = [];
|
||||||
|
|
||||||
|
export const parse =
|
||||||
|
(parseTo: (...args: any[]) => any) =>
|
||||||
|
(target: any, propertyName: string, index: number) => {
|
||||||
|
parseConf[index] = parseTo;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class UserService {
|
||||||
|
private users: NewType[] = [
|
||||||
|
{ id: 1, username: 'admin' },
|
||||||
|
{ id: 2, username: 'pincman' },
|
||||||
|
];
|
||||||
|
|
||||||
|
getUsers() {
|
||||||
|
return this.users;
|
||||||
|
}
|
||||||
|
|
||||||
|
@parseDecorator
|
||||||
|
delete(@parse((arg: any) => Number(arg)) id: number) {
|
||||||
|
this.users = this.users.filter((userObj) => userObj.id !== id);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在函数调用前执行格式化操作
|
||||||
|
export const parseDecorator = (
|
||||||
|
target: any,
|
||||||
|
propertyName: string,
|
||||||
|
descriptor: PropertyDescriptor,
|
||||||
|
): PropertyDescriptor => {
|
||||||
|
console.log('开始格式化数据');
|
||||||
|
return {
|
||||||
|
...descriptor,
|
||||||
|
value(...args: any[]) {
|
||||||
|
// 获取格式化后的参数列表
|
||||||
|
const newArgs = args.map((v, i) =>
|
||||||
|
parseConf[i] ? parseConf[i](v) : v,
|
||||||
|
);
|
||||||
|
console.log('格式化完毕');
|
||||||
|
return descriptor.value.apply(this, newArgs);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
41
src/example/exp8.ts
Normal file
41
src/example/exp8.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
export const HiddenDecorator = () => {
|
||||||
|
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
||||||
|
console.log(descriptor);
|
||||||
|
descriptor.enumerable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserEntity {
|
||||||
|
private _nickname: string;
|
||||||
|
// @ts-ignore
|
||||||
|
private fullName: string;
|
||||||
|
|
||||||
|
@HiddenDecorator()
|
||||||
|
@PrefixDecorator('jesse')
|
||||||
|
get nickname(): string {
|
||||||
|
return this._nickname;
|
||||||
|
}
|
||||||
|
|
||||||
|
set nickname(value: string) {
|
||||||
|
this._nickname = value;
|
||||||
|
this.fullName = `${value}-fullname`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const exp8 = () => {
|
||||||
|
const user = new UserEntity();
|
||||||
|
user.nickname = 'leon';
|
||||||
|
console.log(user.nickname);
|
||||||
|
console.log(Object.keys(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
function PrefixDecorator(prefix: string): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor {
|
||||||
|
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
||||||
|
return {
|
||||||
|
...descriptor,
|
||||||
|
set(v) {
|
||||||
|
descriptor.set.apply(this, [`${prefix}_${v}`])
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
19
src/main.ts
Normal file
19
src/main.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
|
||||||
|
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter(), {
|
||||||
|
cors: true,
|
||||||
|
logger: ['error', 'warn']
|
||||||
|
});
|
||||||
|
app.setGlobalPrefix('api');
|
||||||
|
|
||||||
|
await app.listen(3000, () => {
|
||||||
|
console.log('Application is running on: http://localhost:3000');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
bootstrap();
|
11
src/modules/content/constants.ts
Normal file
11
src/modules/content/constants.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export enum PostBodyType {
|
||||||
|
HTML = 'html',
|
||||||
|
MD = 'markdown',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PostOrderType {
|
||||||
|
CREATED = 'createdAt',
|
||||||
|
UPDATED = 'updatedAt',
|
||||||
|
PUBLISHED = 'publishedAt',
|
||||||
|
CUSTOM = 'custom'
|
||||||
|
}
|
16
src/modules/content/content.module.ts
Normal file
16
src/modules/content/content.module.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { PostController } from './controllers/post.controller';
|
||||||
|
import { PostEntity } from './entities/post.entity';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { DatabaseModule } from './../database/database.module';
|
||||||
|
import { PostService } from './services/post.service';
|
||||||
|
import { PostRepository } from './repositories/post.repository';
|
||||||
|
import { SanitizeService } from './services/sanitize.service';
|
||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([PostEntity]), DatabaseModule.forRepository([PostRepository])],
|
||||||
|
controllers: [PostController],
|
||||||
|
providers: [SanitizeService, PostRepository, PostService],
|
||||||
|
exports: [PostService, DatabaseModule.forRepository([PostRepository])]
|
||||||
|
})
|
||||||
|
export class ContentModule { }
|
52
src/modules/content/controllers/post.controller.ts
Normal file
52
src/modules/content/controllers/post.controller.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Patch, Post, Query } from '@nestjs/common';
|
||||||
|
import { PostService } from '../services/post.service';
|
||||||
|
import { PaginateOptions } from '@/modules/database/types';
|
||||||
|
@Controller('posts')
|
||||||
|
export class PostController {
|
||||||
|
constructor(protected service: PostService) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
@Get()
|
||||||
|
async list(@Query() options: PaginateOptions) {
|
||||||
|
return this.service.paginate(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询单篇文章
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
@Get(':id')
|
||||||
|
async detail(@Param('id', new ParseUUIDPipe()) id: string) {
|
||||||
|
return this.service.detail(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增文章
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
@Post()
|
||||||
|
async store(@Body() data: Record<string, any>) {
|
||||||
|
return this.service.create(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新文章
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
@Patch()
|
||||||
|
async update(@Body() data: Record<string, any>) {
|
||||||
|
return this.service.update(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文章
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
@Delete()
|
||||||
|
async delete(@Param('id', new ParseUUIDPipe()) id: string) {
|
||||||
|
return this.service.delete(id);
|
||||||
|
}
|
||||||
|
}
|
25
src/modules/content/dtos/create-post.dto.ts
Normal file
25
src/modules/content/dtos/create-post.dto.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { IsNotEmpty, IsOptional, MaxLength } from 'class-validator';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CreatePostDto {
|
||||||
|
|
||||||
|
@MaxLength(255, {
|
||||||
|
always: true,
|
||||||
|
message: 'Title is too long',
|
||||||
|
})
|
||||||
|
@IsNotEmpty({ groups: ['create'], message: '帖子标题必须填写' })
|
||||||
|
@IsOptional({ groups: ['update'], })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ groups: ['create'], message: '帖子内容必须填写' })
|
||||||
|
@IsOptional({ groups: ['update'] })
|
||||||
|
body: string;
|
||||||
|
|
||||||
|
@MaxLength(500, {
|
||||||
|
always: true,
|
||||||
|
message: 'Summaries is too long',
|
||||||
|
})
|
||||||
|
@IsOptional({ always: true, })
|
||||||
|
summaries?: string;
|
||||||
|
}
|
15
src/modules/content/dtos/update-post.dto.ts
Normal file
15
src/modules/content/dtos/update-post.dto.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// src/modules/content/dtos/update-post.dto.ts
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { IsDefined, IsNumber } from 'class-validator';
|
||||||
|
|
||||||
|
import { CreatePostDto } from './create-post.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UpdatePostDto extends PartialType(CreatePostDto) {
|
||||||
|
@IsNumber(undefined, { groups: ['update'], message: '帖子ID格式错误' })
|
||||||
|
@IsDefined({ groups: ['update'], message: '帖子ID必须指定' })
|
||||||
|
id: number;
|
||||||
|
}
|
35
src/modules/content/entities/post.entity.ts
Normal file
35
src/modules/content/entities/post.entity.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { PostBodyType } from './../constants';
|
||||||
|
import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryColumn } from "typeorm";
|
||||||
|
|
||||||
|
@Entity('content_posts')
|
||||||
|
export class PostEntity extends BaseEntity {
|
||||||
|
@PrimaryColumn({ type: 'varchar', generated: 'uuid', length: '32' })
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ comment: '文章标题' })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@Column({ comment: '文章内容', type: 'text' })
|
||||||
|
body: string;
|
||||||
|
|
||||||
|
@Column({ comment: '文章描述', nullable: true })
|
||||||
|
summary: string;
|
||||||
|
|
||||||
|
@Column({ comment: '关键字', type: 'simple-array', nullable: true })
|
||||||
|
keywords: string[];
|
||||||
|
|
||||||
|
@Column({ comment: '文章类型', type: 'varchar', default: PostBodyType.MD })
|
||||||
|
type: PostBodyType;
|
||||||
|
|
||||||
|
@Column({ comment: '发布时间', type: 'varchar', nullable: true })
|
||||||
|
publishedAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ comment: '自定义文章排序', default: 0 })
|
||||||
|
customOrder: number
|
||||||
|
|
||||||
|
@CreateDateColumn({ comment: '创建时间' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@CreateDateColumn({ comment: '更新时间' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
10
src/modules/content/repositories/post.repository.ts
Normal file
10
src/modules/content/repositories/post.repository.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { CustomRepository } from '@/modules/database/decorators/repository.decorator';
|
||||||
|
import { PostEntity } from './../entities/post.entity';
|
||||||
|
import { Repository } from "typeorm";
|
||||||
|
|
||||||
|
@CustomRepository(PostEntity)
|
||||||
|
export class PostRepository extends Repository<PostEntity> {
|
||||||
|
buildBaseQB() {
|
||||||
|
return this.createQueryBuilder('post');
|
||||||
|
}
|
||||||
|
}
|
104
src/modules/content/services/post.service.ts
Normal file
104
src/modules/content/services/post.service.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { isFunction, isNil, omit } from 'lodash';
|
||||||
|
import { PostOrderType } from './../constants';
|
||||||
|
import { PaginateOptions, QueryHook } from '@/modules/database/types';
|
||||||
|
import { PostRepository } from './../repositories/post.repository';
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { PostEntity } from '../entities/post.entity';
|
||||||
|
import { EntityNotFoundError, IsNull, Not, SelectQueryBuilder } from 'typeorm';
|
||||||
|
import { paginate } from '@/modules/database/helpers';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PostService {
|
||||||
|
constructor(protected repository: PostRepository) { }
|
||||||
|
/**
|
||||||
|
* 获取分页数据
|
||||||
|
* @param options 分页选项
|
||||||
|
* @param callback 添加额外的查询
|
||||||
|
*/
|
||||||
|
async paginate(options: PaginateOptions, callback?: QueryHook<PostEntity>) {
|
||||||
|
const qb = await this.buildListQuery(this.repository.buildBaseQB(), options, callback);
|
||||||
|
return paginate(qb, options);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 查询单篇文章
|
||||||
|
* @param id 文章id
|
||||||
|
* @param callback 额外的查询
|
||||||
|
*/
|
||||||
|
async detail(id: string, callback?: QueryHook<PostEntity>) {
|
||||||
|
let qb = this.repository.buildBaseQB();
|
||||||
|
qb.where(`post.id = :id`, { id });
|
||||||
|
qb = !isNil(callback) && isFunction(callback) ? await callback(qb) : qb;
|
||||||
|
const item = await qb.getOne();
|
||||||
|
if (!item) {
|
||||||
|
throw new EntityNotFoundError(PostEntity, `Post with id ${id} not found`);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 创建文章
|
||||||
|
* @param data 文章
|
||||||
|
*/
|
||||||
|
async create(data: Record<string, any>) {
|
||||||
|
const item = await this.repository.save(data);
|
||||||
|
return this.detail(item.id)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 更新文章
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
async update(data: Record<string, any>) {
|
||||||
|
await this.repository.update(data.id, omit(data, ['id']));
|
||||||
|
return this.detail(data.id);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 删除文章
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
async delete(id: string) {
|
||||||
|
const item = await this.repository.findOneByOrFail({ id });
|
||||||
|
return this.repository.remove(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建文章列表查询器
|
||||||
|
* @param qb 初始查询构造器
|
||||||
|
* @param options 排查分页选项后的查询选项
|
||||||
|
* @param callback 添加额外的查询
|
||||||
|
*/
|
||||||
|
buildListQuery(qb: SelectQueryBuilder<PostEntity>, options: Record<string, any>, callback: QueryHook<PostEntity>) {
|
||||||
|
const { orderBy, isPublished } = options;
|
||||||
|
if (typeof isPublished === 'boolean') {
|
||||||
|
isPublished ? qb.where({ publishedAt: Not(IsNull()) }) : qb.where({ publishedAt: IsNull() });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queryOrderBy(qb, orderBy);
|
||||||
|
if (callback) return callback(qb);
|
||||||
|
return qb;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 对文章进行排序的query构建
|
||||||
|
* @param qb 查询构造器
|
||||||
|
* @param orderBy 排序方式
|
||||||
|
*/
|
||||||
|
queryOrderBy(qb: SelectQueryBuilder<PostEntity>, orderBy?: PostOrderType) {
|
||||||
|
switch (orderBy) {
|
||||||
|
case PostOrderType.CREATED:
|
||||||
|
qb.orderBy('createdAt', 'DESC');
|
||||||
|
break;
|
||||||
|
case PostOrderType.UPDATED:
|
||||||
|
qb.orderBy('updatedAt', 'DESC');
|
||||||
|
break;
|
||||||
|
case PostOrderType.PUBLISHED:
|
||||||
|
qb.orderBy('publishedAt', 'DESC');
|
||||||
|
break;
|
||||||
|
case PostOrderType.CUSTOM:
|
||||||
|
qb.orderBy('customOrder', 'DESC');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qb.orderBy('createdAt', 'DESC');
|
||||||
|
qb.addOrderBy('updatedAt', 'DESC');
|
||||||
|
qb.addOrderBy('publishedAt', 'DESC');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
src/modules/content/services/sanitize.service.ts
Normal file
24
src/modules/content/services/sanitize.service.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { deepMerge } from "@/modules/core/helpers";
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import sanitizeHtml from "sanitize-html";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SanitizeService {
|
||||||
|
protected config: sanitizeHtml.IOptions = {};
|
||||||
|
constructor() {
|
||||||
|
this.config = {
|
||||||
|
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img', 'code']),
|
||||||
|
allowedAttributes: {
|
||||||
|
...sanitizeHtml.defaults.allowedAttributes,
|
||||||
|
'*': ['class', 'style', 'height', 'width'],
|
||||||
|
},
|
||||||
|
parser: {
|
||||||
|
lowerCaseTags: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitize(body: string, options?: sanitizeHtml.IOptions) {
|
||||||
|
return sanitizeHtml(body, deepMerge(this.config, options ?? {}, 'replace'));
|
||||||
|
}
|
||||||
|
}
|
22
src/modules/content/subscribers/post.subscriber.ts
Normal file
22
src/modules/content/subscribers/post.subscriber.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { PostBodyType } from './../constants';
|
||||||
|
import { DataSource, EventSubscriber } from "typeorm";
|
||||||
|
import { SanitizeService } from "../services/sanitize.service";
|
||||||
|
import { PostRepository } from "../repositories/post.repository";
|
||||||
|
import { PostEntity } from "../entities/post.entity";
|
||||||
|
|
||||||
|
@EventSubscriber()
|
||||||
|
export class PostSubscriber {
|
||||||
|
constructor(protected dataSource: DataSource, protected sanitizeService: SanitizeService, protected postRepository: PostRepository) {
|
||||||
|
dataSource.subscribers.push(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
listenTo() {
|
||||||
|
return PostEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
async afterLoad(entity: PostEntity) {
|
||||||
|
if (entity.type === PostBodyType.HTML) {
|
||||||
|
entity.body = this.sanitizeService.sanitize(entity.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/modules/core/core.module.ts
Normal file
13
src/modules/core/core.module.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Module, DynamicModule } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Module({})
|
||||||
|
export class CoreModule {
|
||||||
|
static forRoot(): DynamicModule {
|
||||||
|
return {
|
||||||
|
module: CoreModule,
|
||||||
|
global: true,
|
||||||
|
providers: [],
|
||||||
|
exports: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
1
src/modules/core/helpers/index.ts
Normal file
1
src/modules/core/helpers/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './utils';
|
25
src/modules/core/helpers/utils.ts
Normal file
25
src/modules/core/helpers/utils.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { isNil } from 'lodash';
|
||||||
|
import deepmerge from 'deepmerge'
|
||||||
|
export function toBoolean(value?: string | boolean): boolean {
|
||||||
|
if (isNil(value)) return false;
|
||||||
|
if (typeof value === 'boolean') return value;
|
||||||
|
try {
|
||||||
|
return JSON.parse(value.toLowerCase());
|
||||||
|
} catch (error) {
|
||||||
|
return value as unknown as boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toNull(value?: string | null): string | null | undefined {
|
||||||
|
return value === 'null' ? null : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deepMerge = <T1, T2>(x: Partial<T1>, y: Partial<T2>, arrayMode: 'replace' | 'merge' = 'merge') => {
|
||||||
|
const options: deepmerge.Options = {};
|
||||||
|
if (arrayMode === 'replace') {
|
||||||
|
options.arrayMerge = (_d, s, _o) => s;
|
||||||
|
} else if (arrayMode === 'merge') {
|
||||||
|
options.arrayMerge = (_d, s, _o) => Array.from(new Set([..._d, ...s]));
|
||||||
|
}
|
||||||
|
return deepmerge(x, y, options) as T2 extends T1 ? T1 : T1 & T2;
|
||||||
|
};
|
1
src/modules/database/constants.ts
Normal file
1
src/modules/database/constants.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const CUSTOM_REPOSITORY_METADATA = 'CUSTOM_REPOSITORY_METADATA';
|
43
src/modules/database/database.module.ts
Normal file
43
src/modules/database/database.module.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { DynamicModule, Module, Provider, Type } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule, TypeOrmModuleOptions, getDataSourceToken } from '@nestjs/typeorm';
|
||||||
|
import { CUSTOM_REPOSITORY_METADATA } from './constants';
|
||||||
|
import { DataSource, ObjectType } from 'typeorm';
|
||||||
|
|
||||||
|
@Module({})
|
||||||
|
export class DatabaseModule {
|
||||||
|
static forRoot(configRegister: () => TypeOrmModuleOptions): DynamicModule {
|
||||||
|
return {
|
||||||
|
module: DatabaseModule,
|
||||||
|
global: true,
|
||||||
|
imports: [TypeOrmModule.forRoot(configRegister())]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static forRepository<T extends Type<any>>(
|
||||||
|
repositories: T[], dataSourceName?: string
|
||||||
|
): DynamicModule {
|
||||||
|
const providers: Provider[] = [];
|
||||||
|
|
||||||
|
for (const Repository of repositories) {
|
||||||
|
const entity = Reflect.getMetadata(CUSTOM_REPOSITORY_METADATA, Repository);
|
||||||
|
if (!entity) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
providers.push({
|
||||||
|
inject: [getDataSourceToken(dataSourceName)],
|
||||||
|
provide: Repository,
|
||||||
|
useFactory: (dataSource: DataSource): InstanceType<typeof Repository> => {
|
||||||
|
const base = dataSource.getRepository<ObjectType<any>>(entity);
|
||||||
|
return new Repository(base.target, base.manager, base.queryRunner);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
module: DatabaseModule,
|
||||||
|
global: true,
|
||||||
|
providers
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
5
src/modules/database/decorators/repository.decorator.ts
Normal file
5
src/modules/database/decorators/repository.decorator.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { CUSTOM_REPOSITORY_METADATA } from './../constants';
|
||||||
|
import { SetMetadata } from '@nestjs/common';
|
||||||
|
import { ObjectType } from 'typeorm';
|
||||||
|
export const CustomRepository = <T>(entity: ObjectType<T>): ClassDecorator =>
|
||||||
|
SetMetadata(CUSTOM_REPOSITORY_METADATA, entity);
|
27
src/modules/database/helpers.ts
Normal file
27
src/modules/database/helpers.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { isNil } from 'lodash';
|
||||||
|
import { PaginateOptions, PaginateResult } from './types';
|
||||||
|
import { ObjectLiteral } from 'typeorm';
|
||||||
|
import { SelectQueryBuilder } from 'typeorm';
|
||||||
|
export const paginate = async<E extends ObjectLiteral>(qb: SelectQueryBuilder<E>, options: PaginateOptions): Promise<PaginateResult<E>> => {
|
||||||
|
const limit = isNil(options.limit) || options.limit < 1 ? 1 : options.limit;
|
||||||
|
const page = isNil(options.page) || options.page < 1 ? 1 : options.page;
|
||||||
|
const start = page >= 1 ? page - 1 : 0;
|
||||||
|
const totalItems = await qb.getCount();
|
||||||
|
qb.take(limit).skip(start * limit);
|
||||||
|
const items = await qb.getMany();
|
||||||
|
const totalPages = totalItems % limit === 0
|
||||||
|
? Math.floor(totalItems / limit)
|
||||||
|
: Math.floor(totalItems / limit) + 1;
|
||||||
|
const remainder = totalItems % limit !== 0 ? totalItems % limit : limit;
|
||||||
|
const itemCount = page < totalPages ? limit : remainder;
|
||||||
|
return {
|
||||||
|
data: items,
|
||||||
|
meta: {
|
||||||
|
totalItems,
|
||||||
|
itemCount,
|
||||||
|
perPage: limit,
|
||||||
|
totalPages,
|
||||||
|
currentPage: page,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/modules/database/types.ts
Normal file
46
src/modules/database/types.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { ObjectLiteral, SelectQueryBuilder } from "typeorm";
|
||||||
|
|
||||||
|
export type QueryHook<Entity> = (
|
||||||
|
qb: SelectQueryBuilder<Entity>
|
||||||
|
) => Promise<SelectQueryBuilder<Entity>>;
|
||||||
|
|
||||||
|
export interface PaginateMeta {
|
||||||
|
/**
|
||||||
|
* 当前页项目数量
|
||||||
|
*/
|
||||||
|
itemCount: number;
|
||||||
|
/**
|
||||||
|
* 项目总数量
|
||||||
|
*/
|
||||||
|
totalItems?: number;
|
||||||
|
/**
|
||||||
|
* 每页显示数量
|
||||||
|
*/
|
||||||
|
perPage: number;
|
||||||
|
/**
|
||||||
|
* 总页数
|
||||||
|
*/
|
||||||
|
totalPages?: number;
|
||||||
|
/**
|
||||||
|
* 当前页数
|
||||||
|
*/
|
||||||
|
currentPage: number;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginateOptions {
|
||||||
|
/**
|
||||||
|
* 当前页数
|
||||||
|
*/
|
||||||
|
page?: number;
|
||||||
|
/**
|
||||||
|
* 每页显示数量
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface PaginateResult<T extends ObjectLiteral> {
|
||||||
|
data: T[];
|
||||||
|
meta: PaginateMeta;
|
||||||
|
}
|
14
src/types/demo1.ts
Normal file
14
src/types/demo1.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
interface SquareConfig {
|
||||||
|
color?: string;
|
||||||
|
width?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSquare(config: SquareConfig): { color: string; area: number } {
|
||||||
|
return {
|
||||||
|
color: config.color || 'red',
|
||||||
|
area: config.width ? config.width * config.width : 20
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let config = { colour: 'red', width: 100 };
|
||||||
|
|
||||||
|
let mySquare = createSquare(config);
|
36
src/types/global.d.ts
vendored
Normal file
36
src/types/global.d.ts
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
declare type RecordAny = Record<string, any>;
|
||||||
|
declare type RecordNever = Record<never, never>;
|
||||||
|
declare type RecordAnyOrNever = RecordAny | RecordNever;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础类型接口
|
||||||
|
*/
|
||||||
|
declare type BaseType = boolean | number | string | undefined | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环境变量类型转义函数接口
|
||||||
|
*/
|
||||||
|
declare type ParseType<T extends BaseType = string> = (value: string) => T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一个类的类型
|
||||||
|
*/
|
||||||
|
declare type ClassType<T> = new (...args: any[]) => T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 嵌套对象全部可选
|
||||||
|
*/
|
||||||
|
declare type RePartial<T> = {
|
||||||
|
[P in keyof T]?: T[P] extends (infer U)[] | undefined
|
||||||
|
? RePartial<U>[]
|
||||||
|
: T[P] extends object | undefined
|
||||||
|
? T[P] extends ((...args: any[]) => any) | ClassType<T[P]> | undefined
|
||||||
|
? T[P]
|
||||||
|
: RePartial<T[P]>
|
||||||
|
: T[P];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防止swc下循环依赖报错
|
||||||
|
*/
|
||||||
|
declare type WrapperType<T> = T;
|
22
test/app.e2e-spec.ts
Normal file
22
test/app.e2e-spec.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import request from 'supertest';
|
||||||
|
|
||||||
|
import { AppModule } from '../src/app.module';
|
||||||
|
|
||||||
|
describe('AppController (e2e)', () => {
|
||||||
|
let app: INestApplication;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||||
|
imports: [AppModule],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
app = moduleFixture.createNestApplication();
|
||||||
|
await app.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('/ (GET)', () => {
|
||||||
|
return request(app.getHttpServer()).get('/').expect(200).expect('Hello World!');
|
||||||
|
});
|
||||||
|
});
|
9
test/jest-e2e.json
Normal file
9
test/jest-e2e.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"moduleFileExtensions": ["js", "json", "ts"],
|
||||||
|
"rootDir": ".",
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"testRegex": ".e2e-spec.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
}
|
||||||
|
}
|
4
tsconfig.build.json
Normal file
4
tsconfig.build.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts", "**.js"]
|
||||||
|
}
|
39
tsconfig.json
Normal file
39
tsconfig.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"alwaysStrict": true,
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"incremental": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strictNullChecks": false,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strictBindCallApply": false,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": false,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"pretty": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"importsNotUsedAsValues": "remove",
|
||||||
|
"noEmit": false,
|
||||||
|
"lib": ["esnext", "DOM", "ScriptHost", "WebWorker"],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src", "test", "typings/**/*.d.ts", "**.js"]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user