first commit

main
rustdreamer 3 years ago
commit 696ec918e8

@ -0,0 +1,120 @@
---
kind: pipeline
name: default
volumes:
- name: dockersock
host:
path: /var/run/docker.sock
- name: cache
host:
path: /tmp/cache/nanjing
steps:
- name: restore-cache
pull: if-not-exists
image: drillster/drone-volume-cache
settings:
restore: true
mount:
- ./.npm-cache
- ./node_modules
volumes:
- name: cache
path: /cache
- name: install
image: node:14.16.1
pull: if-not-exists
commands:
# 删除文件 npm install
# - rm -rf package-lock.json
- npm --registry https://registry.npm.taobao.org install
when:
event: [push, pull_request]
branch: [development, test, master, sso]
- name: rebuild-cache
pull: if-not-exists
image: drillster/drone-volume-cache
settings:
rebuild: true
mount:
- ./.npm-cache
- ./node_modules
volumes:
- name: cache
path: /cache
# build
- name: build
pull: if-not-exists
image: node:14.16.1
commands:
- npm run build
when:
event: [push, pull_request]
branch: [development, test, master, sso]
# docker-build
- name: docker-build
pull: if-not-exists
image: plugins/docker
volumes:
- name: dockersock
path: /var/run/docker.sock
settings:
repo: hub.kaiguawang.cn/qunsense/api
registry: hub.kaiguawang.cn
tags:
- latest
- '0.0.1'
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
event: [push, pull_request]
branch: [development, test, master]
# run
- name: dev-run
image: appleboy/drone-ssh
pull: if-not-exists
settings:
host: 172.17.0.1
username: root
password:
from_secret: ssh_password
port: 22
command_timeout: 300s
script:
- cd /home/runtime/apps/qunsense
- docker compose up -d api
when:
event: [push, pull_request]
branch: [development]
- name: notify
image: fifsky/drone-wechat-work
pull: if-not-exists
# pull: always
settings:
url: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4166884e-9d7b-4e7a-a1c8-80d71d4755ba
msgtype: markdown
content: |
{{if eq .Status "success" }}
#### 🎉 ${DRONE_REPO} 构建成功
> Commit: ${DRONE_COMMIT_MESSAGE}
> Author: ${DRONE_COMMIT_AUTHOR}
{{else}}
#### ❌ ${DRONE_REPO} 构建失败
> Commit: ${DRONE_COMMIT_MESSAGE}
> Author: ${DRONE_COMMIT_AUTHOR}
> 请立即修复!!!
{{end}}
when:
status:
- failure
- success

@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

46
.gitignore vendored

@ -0,0 +1,46 @@
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# project-tree
.git
dist
node_modules
.upload
# Tests
/coverage
/.nyc_output
.history
# 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
upload/**/*
./swagger-spec.json

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

@ -0,0 +1,30 @@
FROM node:14.16.1
ARG NODE_ENV=development
ENV NODE_ENV=$NODE_ENV
# Set a working directory
WORKDIR /usr/src/app
COPY ./cert cert
# Install Node.js dependencies
COPY package.json yarn.lock ./
RUN npm config set registry https://registry.npm.taobao.org
#RUN npm config set unsafe-perm true
COPY ./patches patches
RUN npm install --production;
RUN npm run postinstall;
RUN mkdir upload && mkdir upload/file
RUN touch swagger-spec.json && chmod 777 swagger-spec.json
RUN chmod 777 -R upload
RUN touch error.log && chmod 777 error.log
COPY dist* ./
RUN ls
COPY production.env production.env
COPY production.env development.env
# Run the container under "node" user by default
USER node
CMD [ "node", "src/main.js" ]

@ -0,0 +1,165 @@
<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.
## node version
```js
>=16.15
```
## Installation
```bash
$ yarn install
```
## Running the app
```bash
# development
$ yarn run start
# watch mode
$ yarn run start:dev
# production mode
$ yarn run start:prod
```
## 运行迁移文件
```bash
$ yarn run migration:run
```
## Test
```bash
# unit tests
$ yarn run test
# e2e tests
$ yarn run test:e2e
# test coverage
$ yarn run test:cov
```
## migration 迁移
1. 创建迁移文件
```bash
$ yarn run migration:create ./migrations/fileName # fileName 就是要创建的文件名,成功后会在migrations文件夹下生成${timestamp}-fileName.ts的迁移文件
```
2. 运行迁移文件
```bash
$ yarn run migration:run #该命令会运行所有的迁移文件,运行过的不会运行
```
3. 回退迁移
```bash
$ yarn run migration:revert #该命令会回退最近一个迁移,如果要退回到某个版本必须多次运行
```
## questions
1. 想Joi、dotenv等包应该是升级到最新版本了es6导入方式请使用
```js
import * as Joi from 'Joi'
```
### 迁移问题
开发环境中可以使用synchronize: true,实时同步数据库中字段但在生产环境中请禁用该配置如果在开发环境中使用该配置修改数据库字段类型时可能导致数据丢失请使migrations
#### migrations
使用migration迁移时可能报
```bash
con not find module '../xxx'
```
解决方案:项目中请使用相对路径导入例如:'import xxx from '../../xxx',当然migration只涉及到entity文件和migrations中的迁移文件所以请保证这些文件使用相对路径导入的方式
### 目录结构
```bash
api
├─ .drone.yml
├─ .eslintrc.js
├─ .prettierrc
├─ Dockerfile
├─ README.md
├─ development.env # 开发环境配置文件
├─ dump.rdb
├─ migrations # 迁移文件
├─ nest-cli.json
├─ package-lock.json
├─ package.json
├─ patches
│ └─ co-wechat-api+3.11.0.patch
├─ production.env # 生产环境配置文件
├─ src #主程序
│ ├─ ali-apy # 阿里云支付
│ │ ├─ ali-pay-api.ts
│ │ └─ index.ts
│ ├─ app.controller.spec.ts
│ ├─ app.controller.ts
│ ├─ app.module.ts
│ ├─ app.service.ts
│ ├─ auth # 用户鉴权和登录
│ ├─ comment # 测试者测试项目的留言模块
│ ├─ common # 一些公用模块
│ ├─ config # 数据库等配置文件
│ ├─ file # 文件上传模块
│ ├─ industry # 系统配置中行业选项
│ ├─ invoicing # 开发票记录
│ ├─ job # 系统配置 职业经历配置
│ ├─ lib
│ ├─ main.ts
│ ├─ project # 企业端项目模块
│ ├─ project-module # 项目组件
│ ├─ project-module-templage # 系统配置项目模版组件
│ ├─ project-template # 系统配置项目模版,包含测试者模拟测试项目及企业端创建项目时选择的模版
│ ├─ question # 系统配置测试者问题模块
│ ├─ recharge-give-count-history # 企业充值或者赠送点数给企业的记录
│ ├─ recharge-order # 企业充值订单
│ ├─ recruit-tester # 招募测试者
│ ├─ redis # redis
│ ├─ simulate-result # 测试者模拟测试回答组件模块
├─ simulate-result # 测试者模拟测试项目
│ ├─ statics
│ ├─ team # 团队模块
│ ├─ team-member # 团队成员
│ ├─ tester # 测试者接受测试项目
│ ├─ tester-account # 测试者账户模块
│ ├─ tester-anwser # 测试者正是式测试回答组件
│ ├─ tester-project # 推送给测试者的项目列表
│ ├─ user # 系统用户
│ └─ wechat # 微信支付
├─ swagger-spec.json
├─ test # 集成测试
│ ├─ app.e2e-spec.ts
│ └─ jest-e2e.json
├─ test.env
├─ tsconfig.build.json
├─ tsconfig.json
├─ upload # 上传的文件
│ └─ file
└─ yarn.lock
```

@ -0,0 +1,15 @@
REDIS_PORT=6379
REDIS_HOST=127.0.0.1
REDIS_FAMILY=4
REDIS_DB=0
# REDIS_PASSWORD=
DOC=true
CLIENT_URI=https://liulinlin.test.qunsense.com/
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USERNAME=api-visitor
MYSQL_PASSWORD=api-visitor
MYSQL_DATABASE=api-visitor

Binary file not shown.

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

18674
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,111 @@
{
"name": "api",
"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",
"postinstall": "patch-package",
"migration:create": "typeorm migration:create",
"migration:run": "npx typeorm-ts-node-esm migration:run -d src/config/config.mysql.ts",
"migration:revert": "npx typeorm-ts-node-esm migration:revert -d src/config/config.mysql.ts"
},
"dependencies": {
"@nestjs/bull": "^0.6.3",
"@nestjs/common": "^9.0.0",
"@nestjs/core": "^9.0.0",
"@nestjs/jwt": "^10.0.3",
"@nestjs/passport": "^9.0.3",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/schedule": "^3.0.0",
"@nestjs/swagger": "^6.3.0",
"@nestjs/typeorm": "^9.0.1",
"@types/cron": "^2.0.1",
"ali-oss": "^6.17.1",
"alipay-sdk": "^3.4.0",
"axios": "^1.4.0",
"bcryptjs": "^2.4.3",
"bluebird": "^3.7.2",
"bull": "^4.10.4",
"cheerio": "^1.0.0-rc.12",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"co-wechat-api": "^3.11.0",
"dotenv": "^16.0.3",
"global": "^4.4.0",
"ioredis": "^5.3.2",
"joi": "^17.9.2",
"moment": "^2.29.4",
"mysql": "^2.18.1",
"nest-wechatpay-node-v3": "^1.0.1",
"nest-winston": "^1.9.3",
"node-xlsx": "^0.23.0",
"passport": "^0.6.0",
"passport-jwt": "^4.0.1",
"patch-package": "^7.0.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0",
"simple-flakeid": "^0.0.5",
"superagent": "^8.0.9",
"tencentcloud-sdk-nodejs": "^4.0.607",
"tencentcloud-sdk-nodejs-faceid": "^4.0.609",
"tree": "^0.1.3",
"typeorm": "0.3.15",
"uuid": "^9.0.0",
"wechatpay-node-v3": "^2.1.4"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@types/bull": "^4.10.0",
"@types/express": "^4.17.13",
"@types/jest": "29.5.0",
"@types/node": "18.15.11",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "29.5.0",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "29.0.5",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.2.0",
"typescript": "^4.7.4"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

@ -0,0 +1,22 @@
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!');
});
});
});

@ -0,0 +1,33 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import * as fs from 'fs';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get("/doc/json")
getDoc(): string {
const json = fs.readFileSync("swagger-spec.json", "utf-8");
return JSON.parse(json);
}
@Get("/app.config")
appConfig() {
return {
app_id: process.env.QUNSENSE_SUITE_ID
}
}
// @Get("/WW_verify_n9zX2u8E9ShFgYmx.txt")
// config(): string {
// return "n9zX2u8E9ShFgYmx";
// }
}

@ -0,0 +1,25 @@
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import {mysql_config} from './config/config.mysql';
import { ConfigModule } from './config/config.module';
import { AuthModule } from './auth/auth.module';
import { UserModule } from './user/user.module';
import { RoleModule } from './role/role.module';
import { FileModule } from './file/file.module';
@Module({
imports: [
ConfigModule,
AuthModule,
UserModule,
TypeOrmModule.forRoot(mysql_config),
ScheduleModule.forRoot(),
RoleModule,
FileModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

@ -0,0 +1,10 @@
import { Injectable } from '@nestjs/common';
import * as dotenv from 'dotenv';
dotenv.config({ path: `${process.env.NODE_ENV || 'development'}.env` });
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

@ -0,0 +1,56 @@
import {
Controller,
Get,
UseGuards,
Post,
Put,
UsePipes,
Body,
Query,
HttpCode,
Res,
Req,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import fs from 'fs';
import { ValidationPipe, User, Roles, RolesGuard } from '../common';
import { AuthService } from './auth.service';
import { Login, PollingDto, LoginMobile, ResponseType, UpdateUserDto, IsLogin, QrResponse, SureLogin, IsLoginResult, LoginStatus, } from './auth.params';
import { UserEntity } from '../user/user.entity';
import redis from 'src/redis';
import { v4 as uuid } from 'uuid';
import Axios from 'axios';
@ApiTags('用户登录')
@Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>,
) { }
@Get('login/status/is_login')
@ApiOperation({ summary: '获取 token 是否失效' })
@UsePipes(new ValidationPipe())
@ApiResponse({
status: 200,
description: '返回参数说明',
type: LoginStatus,
})
async loginGetStatus(@Query() loginData: IsLogin): Promise<any> {
const result = await redis.get(`qunsense_code:${loginData.qunsense_code}`)
if (result) {
return {
status: true
}
}
return {
status: false
}
}
}

@ -0,0 +1,33 @@
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthController } from './auth.controller';
import { AuthService, expires_in } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserEntity } from '../user/user.entity';
// import AuthResolver from './auth.resolver';
// import { RoleEntity } from '../role/role.entity';
// import { RoleItemEntity } from '../role-item/role-item.entity';
@Module({
imports: [
TypeOrmModule.forFeature([
UserEntity,
// RoleEntity,
// RoleItemEntity,
]),
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secret: 'secretKey',
signOptions: {
expiresIn: expires_in,
// expiresIn: 3600,
},
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],//, AuthResolver],
exports: [AuthService],
})
export class AuthModule {}

@ -0,0 +1,7 @@
export class AccessToken {
public user_id?: string;
public expires_in?: number;
public access_token?: string;
}

@ -0,0 +1,169 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
IsString,
IsInt,
IsNotEmpty,
IsOptional,
IsUUID,
Length,
isNumber,
IsNumber,
} from 'class-validator';
export class Login {
@ApiProperty({ description: '从小程序端获取的code' })
@IsString()
@IsNotEmpty()
public code: string;
@ApiProperty({ description: '小程序码' })
@IsString()
@IsOptional()
public qunsense_code: string;
}
export class SureLogin {
@ApiProperty({ description: '小程序码' })
@IsString()
@IsOptional()
public qunsense_code: string;
}
export class IsLogin {
@ApiProperty({ description: '小程序码' })
@IsString()
@IsNotEmpty()
public qunsense_code: string;
}
export class LoginMobile {
@ApiProperty({ description: '从小程序端获取的code' })
@IsString()
@IsNotEmpty()
public code: string;
@ApiPropertyOptional({ description: '昵称' })
@IsString()
@IsOptional()
public nickName?: string;
@ApiPropertyOptional({ description: '头像url' })
@IsString()
@IsOptional()
public avatarUrl?: string;
@ApiProperty({ description: '小程序码' })
@IsString()
@IsOptional()
public qunsense_code: string;
}
export class UpdateUserDto {
@ApiProperty({ description: '登录时返回的用户id' })
@IsString()
@IsNotEmpty()
public id: string;
@ApiProperty({ description: '昵称' })
@IsString()
@IsOptional()
public nickName: string;
@ApiProperty({ description: '头像url' })
@IsString()
@IsOptional()
public avatarUrl: string;
@ApiProperty({ description: '扫码的唯一随机字符串' })
@IsString()
@IsNotEmpty()
public randomString: string;
}
export class PollingDto {
@ApiProperty({ description: '扫码的唯一随机字符串' })
@IsString()
@IsNotEmpty()
public randomString: string;
}
export class ResponseType {
/**
* access_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRmZjZjNWU3LTAxMDUtNDgxYS1hYjkwLWUxODM1M2I5OTZjMyIsImlhdCI6MTY4MzIwNTY4MywiZXhwIjoxNjgzNTY1NjgzfQ.HxaFrRLdZr5U08EHeMQXuZjAFIrNSgXCtFjGIK0y3ko"
created_date: "2023-05-04T13:08:04.000Z"
expires_in: 2592000
user_id: "4ff6c5e7-0105-481a-ab90-e18353b996c3"
*/
@ApiPropertyOptional({
description:
'接口调用凭证,如果返回该字段说明用户已经更新了头像和昵称,可以直接登录,否则需要更新头像',
})
@IsString()
public access_token: string;
@ApiPropertyOptional({ description: '创建时间' })
@IsString()
public created_date?: string;
@ApiPropertyOptional({ description: '有效时间' })
@IsNumber()
public expires_in: number;
@ApiProperty({ description: '用户id' })
@IsString()
public user_id: string;
}
export class LoginStatus {
@ApiPropertyOptional({ description: '登录状态返回' })
public status: boolean;
}
export class IsLoginResult {
@ApiPropertyOptional({ description: '错误信息' })
@IsString()
public msg: string;
@ApiPropertyOptional({
description: '状态码 200 登录成功 400 已失效 500 系统错误 401 未登录',
})
@IsNumber()
public code?: string;
@ApiPropertyOptional({ description: '是否登录' })
@IsNumber()
public success: boolean;
@ApiPropertyOptional({
description:
'接口调用凭证,如果返回该字段说明用户已经更新了头像和昵称,可以直接登录,否则需要更新头像',
required: false,
})
@IsString()
public access_token?: string;
@ApiPropertyOptional({ description: '创建时间', required: false })
@IsString()
public created_date?: string;
@ApiPropertyOptional({ description: '有效时间', required: false })
@IsNumber()
public expires_in?: number;
@ApiProperty({ description: '用户id', required: false })
@IsString()
public user_id?: string;
}
export class QrResponse {
@ApiPropertyOptional({ description: '图片 base64' })
@IsString()
public image: string;
@ApiPropertyOptional({ description: '图片类型' })
@IsString()
public extension?: string;
@ApiPropertyOptional({ description: '登录码' })
@IsString()
public code?: string;
}

@ -0,0 +1,82 @@
import {
Injectable,
UnauthorizedException,
BadRequestException,
Logger,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcryptjs';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, In } from 'typeorm';
import { JwtPayload, UserJwtPayload } from './interfaces/jwt-payload.interface';
import { UserEntity } from '../user/user.entity';
import { Login, PollingDto, LoginMobile, UpdateUserDto, IsLogin } from './auth.params';
import redis from 'src/redis';
// import { RoleItemEntity } from '../role-item/role-item.entity';
// import { RoleEntity } from '../role/role.entity';
export const expires_in = 3600 * 24 * 30;
@Injectable()
export class AuthService {
constructor(
private readonly jwtService: JwtService,
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>,
// @InjectRepository(RoleItemEntity)
// private readonly roleItemRepository: Repository<RoleItemEntity>,
// @InjectRepository(RoleEntity)
// private readonly roleRepository: Repository<RoleEntity>,
) { }
async verify(token: string) {
const result = this.jwtService.verify(token.replace('Bearer ', ''));
const user = await this.validateUser(result);
return {
...result,
...user,
};
}
async createUserToken(user: UserJwtPayload) {
const access_token = this.jwtService.sign({ id: user.id });
return {
expires_in,
access_token,
user_id: user.id,
created_date: new Date(),
};
}
async validateUser(payload: UserJwtPayload): Promise<any> {
console.log(payload)
const user = await this.userRepository.findOne({ where: { id: payload.id } })
if (!user) throw new BadRequestException('用户不存在')
return { id: payload.id };
}
async signInUser(user: UserEntity): Promise<string> {
return this.jwtService.sign({
id: user.id,
roles: ['user'],
});
}
async polling(query: PollingDto) {
const id = await redis.get(query.randomString)
if (id) {
redis.del(query.randomString)
const user = await this.userRepository.findOne({ where: { id: id } })
const access_token = await this.createUserToken(user);
return {
...access_token,
user_id: user.id,
};
} else {
throw new BadRequestException()
}
}
}

@ -0,0 +1,23 @@
import {
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
// console.log('content',context)
// add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
return super.canActivate(context);
}
handleRequest(err, user, info) {
if (err || !user) {
throw err || new UnauthorizedException();
}
return user;
}
}

@ -0,0 +1,8 @@
export interface JwtPayload {
id: string;
email: string;
}
export interface UserJwtPayload {
id: string;
}

@ -0,0 +1,32 @@
import {
Injectable,
UnauthorizedException,
ExecutionContext,
CanActivate,
} from '@nestjs/common';
import { PassportStrategy, AuthGuard } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './auth.service';
import { JwtPayload } from './interfaces/jwt-payload.interface';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'secretKey',
});
}
async validate(payload: JwtPayload) {
const user = await this.authService.validateUser(payload);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}

@ -0,0 +1,38 @@
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
// console.log(exception)
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
if (request) {
// console.log('exception', exception);
const status = exception.getStatus();
if (!request.url.startsWith('/graphql')) {
return response.status(status).json(exception);
// return response.status(status).json({
// statusCode: status,
// timestamp: new Date().toISOString(),
// path: request.url,
// });
}
}
// const gqlHost = GqlArgumentsHost.create(host);
// console.log(11111, { ...exception, message: exception.message.message });
// FIXME
/* tslint:disable-next-line */
// @ts-ignore
exception.message = exception.message || exception.message;
// return { ...exception, message: exception.message.message };
return exception;
}
}

@ -0,0 +1,36 @@
import { ApiProperty } from '@nestjs/swagger';
import {
BaseEntity,
Entity,
CreateDateColumn,
UpdateDateColumn,
PrimaryGeneratedColumn,
PrimaryColumn,
Column,
BeforeInsert,
} from 'typeorm';
// import { ObjectType, Field } from '@nestjs/graphql';
const snowId = require('simple-flakeid')
let gen1 = new snowId.SnowflakeIdv1({ workerId: 1, seqBitLength: 2, workerIdBitLength: 1 })
// @ObjectType()
@Entity()
export class Base extends BaseEntity {
@BeforeInsert()
generateId() {
this.id = String(gen1.NextId())
}
// @Field()
@PrimaryColumn({ length: 12 })
// @Column({ type: 'varchar', length: '12', nullable: false })
@ApiProperty({ description: "数据唯一id" })
id: string;
// id: number;
@CreateDateColumn()
@ApiProperty({ description: "创建时间" })
created_date: Date;
@UpdateDateColumn()
@ApiProperty({ description: "更新时间" })
updated_date: Date;
}

@ -0,0 +1,116 @@
import { v4 as uuid } from 'uuid';
import fs from 'fs';
import path from 'path';
// import xlsx from 'node-xlsx';
// import { fileClient } from '../lib/oss';
// import redis from '../redis';
import { getRepository } from 'typeorm';
// import { TeamEntity } from '../team/team.entity';
// import { MemberEntity } from '../member/member.entity';
import { eight_hour } from './const';
export function formatArgs(args: any) {
const newArgs = {
...args,
before: args.before && new Buffer(args.before, 'base64').toString(),
after: args.after && new Buffer(args.after, 'base64').toString(),
};
return newArgs;
}
export const changeName = (fileName: string) => {
// var fileName = fileName.replace(/\s+/g, '-')
// const fileName = fileName;
const dotPosition = fileName.lastIndexOf('.');
let name = fileName;
if (dotPosition > 0) {
name = fileName.substring(0, dotPosition);
}
let extension = '';
if (dotPosition !== -1) {
extension = fileName.substring(dotPosition);
}
// return Date.now() + extension;
return name + '-' + Date.now() + extension;
};
// export const createXlsx = (base, data, token, name, columns) => {
// return setTimeout(async () => {
// try {
// columns = columns.filter(column => column !== '背景信息');
// base = base.map(base1 => {
// if (
// base1 === '是否愿意参加区域决赛前培训' &&
// columns.includes('是否愿意参加总决赛前培训')
// ) {
// return '是否愿意参加总决赛前培训';
// }
// if (base1 === '比赛结果' && columns.includes('区域决赛结果')) {
// return '区域决赛结果';
// }
// if (base1 === '比赛结果' && columns.includes('总决赛结果')) {
// return '总决赛结果';
// }
// return base1;
// });
// const new_data = data.map(item => {
// const new_item = columns.map(column => {
// let column_index = 0;
// base.forEach((rr, index) => {
// if (rr === column) {
// column_index = index;
// }
// });
// return item[column_index];
// });
// return new_item;
// });
// const file_name = '/xlsx/' + token + uuid() + '.xlsx';
// const buffer = xlsx.build([
// { name: name || 'Sheet1', data: [columns, ...new_data] },
// ]);
// const file_path = path.resolve(__dirname, '../puppeteer');
// const local_file_name = `${file_path}/${token}.xlsx`;
// fs.writeFileSync(local_file_name, buffer);
// await fileClient.put(file_name, local_file_name);
// fs.unlinkSync(local_file_name);
// await redis.set(token, file_name, 'EX', 3600);
// } catch (e) {
// console.log(`导出${name}错误`, e);
// }
// }, 100);
// };
// export const getUserTeamId = async (user_id: string) => {
// const user_team = await redis.get(`team:${user_id}`);
// if (!user_team) {
// const user_member = await getRepository(MemberEntity).findOne({
// owner_id: user_id,
// });
// if (!user_member) {
// throw new Error('用户不存在 team');
// }
// await redis.set(`team:${user_id}`, user_member.team_id);
// return user_member.team_id;
// }
// return user_team;
// };
// export const getUserTeamIdNoThrow = async (user_id: string) => {
// const user_team = await redis.get(`team:${user_id}`);
// if (!user_team) {
// const user_member = await getRepository(MemberEntity).findOne({
// owner_id: user_id,
// });
// if (!user_member) {
// return undefined;
// }
// await redis.set(`team:${user_id}`, user_member.team_id);
// return user_member.team_id;
// }
// return user_team;
// };
export const getEightTime = () => {
return new Date(Date.now() + eight_hour);
};

@ -0,0 +1,213 @@
// import {
// ObjectType,
// Field,
// InputType,
// Int,
// registerEnumType,
// ArgsType,
// } from '@nestjs/graphql';
// import Relay from 'graphql-relay';
import { IsUUID, Min, Max, IsNumberString, IsInt, IsArray, IsNumber, IsOptional } from 'class-validator';
import { ApiOperation, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
// export type ConnectionCursor = Relay.ConnectionCursor;
// @ObjectType('Result')
export class Result {
success: boolean;
}
// @ObjectType('Uri')
export class Uri {
// //@Field({ nullable: false })
uri: string;
}
// @ObjectType('OutResult')
export class OutResult {
// //@Field({ nullable: true })
uri: string;
// //@Field({ nullable: true })
token: string;
}
// @ObjectType('OutUriResult')
export class OutUriResult {
// //@Field({ nullable: true })
uri: string;
}
export class BaseList<T> {
// //@Field({ nullable: true })
@ApiProperty({
description:
'总数',
})
count: number;
@ApiProperty({
description:
'列表',
})
list: T[];
}
// @ObjectType('Count')
export class Count {
// //@Field({ nullable: false })
count: number;
}
// @ArgsType()
export class SeeXlsx {
// //@Field({ nullable: false })
token: string;
}
// @InputType('Sort')
class Sort {
// //@Field({ nullable: true })
field: string;
// //@Field({ nullable: true })
input: string;
}
// @InputType('PageArgs')
export class PageArgs {
// //@Field({ nullable: true })
before: string;
// //@Field({ nullable: true })
after: string;
// //@Field({ nullable: true })
fullTextSearch: string;
// //@Field(type => Int, { nullable: true })
first: number;
// //@Field(type => Int, { nullable: true })
last: number;
// //@Field(type => [Sort], { nullable: true })
sort: string;
}
// @ObjectType()
export class PageInfo {
// //@Field({ nullable: true })
startCursor?: string;
//@Field({ nullable: true })
endCursor?: string;
//@Field({ nullable: false })
hasPreviousPage: boolean;
//@Field({ nullable: false })
hasNextPage: boolean;
//@Field({ nullable: false })
totalCount: boolean;
}
export class Connection {
// //@Field(type => Array, { nullable: false })
// edges: ConnectionType[];
// //@Field(type => PageInfo)
//@Field({ nullable: true })
pageInfo: PageInfo;
}
export enum eventType {
register = 'register',
backPassword = 'backPassword',
}
// registerEnumType(eventType, {
// name: 'eventType',
// description: 'eventType',
// });
// @ObjectType('STSTokenType')
export class STSTokenType {
//@Field({ nullable: false })
access_key_id: string; // accessKeyId
//@Field({ nullable: false })
policy: string; // policy规定了请求的表单域的合法性。不包含policy表单域的请求被认为是匿名请求并且只能访问public-read-write的bucket
//@Field({ nullable: false })
signature: string; // 根据Access Key Secret和policy计算的签名信息OSS验证该签名信息从而验证该Post请求的合法性。
//@Field({ nullable: false })
key: string; // 上传文件的object名称
//@Field({ nullable: false })
host: string;
//@Field({ nullable: false })
expiration: Date; // 过期时间
//@Field({ nullable: true })
dir?: string; // 文件夹
//@Field({ nullable: false })
callback: string; // 回调地址
}
// @ArgsType()
// export class Id {
// //@Field({ nullable: false, description: 'order id' })
// @IsUUID()
// id: string;
// }
// @ArgsType()
export class CommonPageArgs {
//@Field(type => Int)
@ApiProperty({ required: false, type: Number, description: '页码' })
@Min(0)
@IsNumber()
skip: number = 0;
//@Field(type => Int)
@ApiProperty({ required: false, type: Number, description: '条数' })
@Min(1)
@Max(50)
@IsNumber()
take: number = 25;
}
// @ArgsType()
export class CommonFilter extends CommonPageArgs {
//@Field(type => String, { nullable: true })
search?: string;
//@Field(type => [String], { nullable: true })
columns?: string;
}
// @ObjectType('List')
export class List {
//@Field({ nullable: false })
count: number;
}
export class UpdateBackDto {
@ApiPropertyOptional()
@IsArray()
@IsOptional()
generatedMaps: any[]
@ApiPropertyOptional()
@IsArray()
@IsOptional()
raw: any[]
@ApiProperty({ description: '受影响的行数' })
@IsNumber()
affected: number
}

@ -0,0 +1,5 @@
export const one_day = 24 * 60 * 60 * 1000;
export const time = 1000 * 60 * 60 * 8;
export const eight_hour = 1000 * 60 * 60 * 8;
export const hour = 1000 * 60 * 60;
export const BCRYPT_HASH_ROUNDS = 10;

@ -0,0 +1,14 @@
import { SetMetadata } from '@nestjs/common';
/* tslint:disable */
export const Roles = (...roles: string[]) => {
console.log(roles)
return SetMetadata('roles', roles)
};
// export const Test = (...args: any[]) => {
// console.log(...args);
// return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
// descriptor.enumerable = false;
// };
// }

@ -0,0 +1,19 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';
// import { GqlExecutionContext } from '@nestjs/graphql';
// import { WsException } from '@nestjs/websockets';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContextHost) => {
const type = ctx.getType();
if (type === 'http') {
const request = ctx.switchToHttp().getRequest();
return request.user;
} else if (type === 'ws') {
const request = ctx.switchToWs().getClient();
return request.user;
} else {
// return GqlExecutionContext.create(ctx).getContext().req.user;
}
},
);

@ -0,0 +1,26 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
let user;
if (request) {
user = request.user;
} else {
// fix graphql 需要特殊处理
user = context.getArgs()[2].req.user;
}
const hasRole = () =>
user.roles.some(role => !!roles.find(item => item === role));
console.log(roles,user.roles)
return user && user.roles && hasRole();
}
}

@ -0,0 +1,10 @@
export * from './decorators/roles.decorator';
export * from './decorators/user.decorator';
export * from './guards/roles.guard';
export * from './validation.pipe';
export * from './params/page.params';
export * from './params/id.params';
export * from './base.entity';
export * from './base.func';
export * from './base.type';
export * from './const';

@ -0,0 +1,18 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable, of } from 'rxjs';
@Injectable()
export class CacheInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const isCached = true;
if (isCached) {
return of([]);
}
return next.handle();
}
}

@ -0,0 +1,18 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
BadGatewayException,
CallHandler,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class ErrorsInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next
.handle()
.pipe(catchError(err => throwError(new BadGatewayException())));
}
}

@ -0,0 +1,111 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Request } from 'express';
import { getMongoRepository } from 'typeorm';
// import { LogMongo } from '../../log/log.mongo';
function createGraphqlLog(request: Request, info, resolve_time: number) {
setImmediate(async () => {
try {
// @ts-ignore
const user = request.user;
// const new_log = new LogMongo();
// new_log.path_name = info.fieldName;
// new_log.parent_type_name = info.parentType.name;
// new_log.field_name = info.fieldName;
// new_log.type = 'graphql';
// new_log.ip = request.ip;
// new_log.resolve_time = resolve_time;
// new_log.content_length = request.headers['content-length'];
// new_log.user_agent = request.headers['user-agent'];
// new_log.sec_fetch_site = String(request.headers['sec-fetch-site']);
// new_log.referer = request.headers.referer;
// new_log.method = request.method;
// if (user) {
// new_log.user_id = user.id;
// new_log.roles = user.roles;
// new_log.permissions = user.permissions;
// }
// if (request.body.query) {
// new_log.query = request.body.query;
// }
// await getMongoRepository(LogMongo, 'mongo').save(new_log);
Logger.debug(`graphql ${info.fieldName} [${resolve_time} ms]`);
} catch (e) {
Logger.error(`http graphql 中间件错误 ${e.message}`);
}
});
}
function createApiLog(request: Request, resolve_time: number) {
setImmediate(async () => {
try {
// @ts-ignore
const user = request.user;
// const new_log = new LogMongo();
// new_log.path_name = request.originalUrl;
// // new_log.parent_type_name = info.parentType.name;
// // new_log.field_name = info.fieldName;
// new_log.type = 'api';
// new_log.ip = request.ip;
// new_log.resolve_time = resolve_time;
// new_log.content_length = request.headers['content-length'];
// new_log.user_agent = request.headers['user-agent'];
// new_log.sec_fetch_site = String(request.headers['sec-fetch-site']);
// new_log.referer = request.headers.referer;
// new_log.method = request.method;
// if (user) {
// new_log.user_id = user.id;
// new_log.roles = user.roles;
// new_log.permissions = user.permissions;
// }
// if (request.body) {
// new_log.body = JSON.stringify(request.body);
// }
// if (request.query) {
// new_log.query = JSON.stringify(request.query);
// }
// if (request.params) {
// new_log.params = JSON.stringify(request.params);
// }
// await getMongoRepository(LogMongo, 'mongo').save(new_log);
Logger.debug(`api ${request.url} [${resolve_time} ms]`);
} catch (e) {
Logger.error(`http api 中间件错误 ${e.message}`);
}
});
}
// 拦截所有的请求 并记录所有请求的时间
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const start = Date.now();
return next.handle().pipe(
tap(() => {
const ctx = context.switchToHttp();
const request = ctx.getRequest<Request>();
const resolve_time = Date.now() - start;
if (!request || request.url.startsWith('/graphql')) {
// @ts-ignore
const info = context.args[3];
// @ts-ignore
const old_request = context.args[2].request;
createGraphqlLog(old_request, info, resolve_time);
} else if (request) {
createApiLog(request, resolve_time);
}
}),
);
}
}

@ -0,0 +1,15 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { timeout } from 'rxjs/operators';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(timeout(5000));
}
}

@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsInt, IsNotEmpty, IsUUID } from 'class-validator';
export class Id {
@ApiProperty()
@IsString()
// @IsUUID()
@IsNotEmpty()
readonly id: string;
}

@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsInt } from 'class-validator';
export class Page {
@ApiProperty()
@IsInt()
readonly skip: number;
@ApiProperty()
@IsInt()
readonly limit: number;
}

@ -0,0 +1,22 @@
// import { CustomScalar, Scalar } from '@nestjs/graphql';
// import { Kind } from 'graphql';
// @Scalar('Date', type => Date)
// export class DateScalar implements CustomScalar<number, Date> {
// description = 'Date custom scalar type';
// parseValue(value: number): Date {
// return new Date(value); // value from the client
// }
// serialize(value: Date): number {
// return value.getTime(); // value sent to the client
// }
// parseLiteral(ast: any): Date {
// if (ast.kind === Kind.INT) {
// return new Date(ast.value);
// }
// return null;
// }
// }

@ -0,0 +1,39 @@
import {
PipeTransform,
Injectable,
ArgumentMetadata,
BadRequestException,
} from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value, { type, metatype }: ArgumentMetadata) {
console.log(value)
if (type === 'query' && value.skip && value.take) {
value = {
...value,
skip: ((parseInt(value.skip, 10) || 1) - 1) * (parseInt(value.take, 10) || 10),
take: parseInt(value.take, 10) || 10,
};
}
if (!metatype || !this.toValidate(metatype)) {
return value;
}
console.log(value)
const object = plainToClass(metatype, value);
const errors = await validate(object);
console.log(errors)
if (errors.length > 0) {
throw new BadRequestException(errors);
// throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.find(type => metatype === type);
}
}

@ -0,0 +1,73 @@
import * as Joi from 'joi';
export const configCheck = {
// NODE_ENV: Joi.string()
// .valid(['development', 'production', 'test', 'provision'])
// .default('development'),
// PORT: Joi.number().default(9999),
// SERVER_URL: Joi.string().required(),
// CLIENT_URL: Joi.string().required(),
// UI_URL: Joi.string().required(),
// // API_RUL: Joi.string().required(),
// PC_URL: Joi.string().required(),
// MOBILE_URL: Joi.string().required(),
// API_AUTH_ENABLED: Joi.boolean().required(),
// SUPER_CODE: Joi.string().required(),
// SMS_ACCESS_KEY_ID: Joi.string().required(),
// SMS_SECRET_ACCESS_KEY: Joi.string().required(),
// SMS_TEMPLATE_CODE: Joi.string().required(),
// SMS_TEMPLATE_FOREIGN_CODE: Joi.string().required(),
// SMS_TEMPLATE_SIGN_NAME: Joi.string().required(),
// SMS_TEMPLATE_CHANGE_DEMAND_STATUS: Joi.string().required(),
// SMS_TEMPLATE_CHANGE_DEMAND_END_DATE: Joi.string().required(),
// SMS_TEMPLATE_SUBSCRIPTION: Joi.string().required(),
// OSS_REGION: Joi.string().required(),
// OSS_ACCESS_KEY_ID: Joi.string().required(),
// OSS_ACCESS_KEY_SECRET: Joi.string().required(),
// OSS_BUCKET: Joi.string().required(),
// OSS_FILE_HOST: Joi.string().required(),
REDIS_PORT: Joi.number().required(),
REDIS_HOST: Joi.string().required(),
REDIS_FAMILY: Joi.number().required(),
REDIS_DB: Joi.number().required(),
// WE_CHAT_APP_ID: Joi.string().required(),
// WE_CHAT_APP_SECRET: Joi.string().required(),
// WE_CHAT_PC_APP_ID: Joi.string().required(),
// WE_CHAT_PC_APP_SECRET: Joi.string().required(),
// WE_CHAT_TOKEN: Joi.string().required(),
// WE_CHAT_PAY_PASSWORD: Joi.string().required(),
// WE_CHAT_PAY_CODE: Joi.string().required(),
// WE_CHAT_PAY_SECRET: Joi.string().required(),
// WE_CHAT_PAY_NOTIFY_URL: Joi.string().required(),
// WE_CHAT_PAY_PLAN_NOTIFY_URL: Joi.string().required(),
// WE_CHAT_UPDATE_PAY_PLAN_NOTIFY_URL: Joi.string().required(),
// ALI_PAY_APP_ID: Joi.string().required(),
// ALI_PAY_NOTIFY_URL: Joi.string().required(),
// ALI_PAY_PLAN_NOTIFY_URL: Joi.string().required(),
// ALI_PAY_UPDATE_PLAN_NOTIFY_URL: Joi.string().required(),
DOC: Joi.boolean().required(),
MYSQL_HOST: Joi.string().required(),
MYSQL_USERNAME: Joi.string().required(),
MYSQL_PASSWORD: Joi.string().required(),
MYSQL_DATABASE: Joi.string().required(),
MYSQL_PORT: Joi.number().required(),
// ENTERPRISE_TOKEN: Joi.string().required(),
// ENTERPRISE_ENCODING_AES_KEY: Joi.string().required(),
// ENTERPRISE_SUITE_ID: Joi.string().required(),
// ENTERPRISE_AGENT_ID: Joi.string().required(),
// ENTERPRISE_SECRET: Joi.string().required(),
// REB_ENVELOPE_TOKEN: Joi.string(),
// REB_ENVELOPE_SUITE_ID: Joi.string(),
// REB_ENVELOPE_SECRET: Joi.string(),
// REB_ENVELOPE_ENCODING_AES_KEY: Joi.string(),
// REB_ENVELOPE_AGENT_ID: Joi.string(),
};

@ -0,0 +1,21 @@
import { DocumentBuilder } from '@nestjs/swagger';
export const options = new DocumentBuilder()
.setTitle('restfull api')
.setDescription(`
Apirestfullapi使headerstatus200get201POST/PUT/PATCH
204deletecodemessageheaderstatus=500 {message:"Internal server error"}
messagestringorm使typeorm@0.3.15iduuidvachar(36)
idER_NO_REFERENCED_ROW2can not add or update a children row:xx
`)
.setVersion('1.0')
// .setTermsOfService('test')
// .setContact('时间伙伴', 'shijianhuoban.com', 'shijianhuoban@outlook.com')
.setLicense('2019 © shijianhuoban', 'https://github.com/shijianhuoban')
.setBasePath('/api')
.addBearerAuth()
// .addOAuth2()
// .addTag('cats')
// addBearerAuth(name?: string, location?: 'header' | 'body' | 'query', type?: string): this;
// addOAuth2(flow?: 'implicit' | 'password' | 'application' | 'accessCode', authorizationUrl?: string, tokenUrl?: string, scopes?: object): this;
.build();

@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
@Module({
providers: [
{
provide: ConfigService,
useValue: new ConfigService(
`${process.env.NODE_ENV || 'development'}.env`,
),
},
],
exports: [ConfigService],
})
export class ConfigModule {}

@ -0,0 +1,53 @@
import { join } from 'path';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { DataSource, DataSourceOptions } from 'typeorm';
import * as dotenv from 'dotenv';
dotenv.config({ path: `${process.env.NODE_ENV || 'development'}.env` });
// import { mongo_entities } from './config.entities'
// const API_STATUS = process.env.API_STATUS;
export const mysql_config: TypeOrmModuleOptions = {
name: 'default',
type: 'mysql',
host: process.env.MYSQL_HOST,
port: Number(process.env.MYSQL_PORT),
username: process.env.MYSQL_USERNAME,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE,
entities: [join(__dirname, '../**/**.entity{.ts,.js}')],
// entities: [join(__dirname, '**/**.entity{.ts,.js}')],
synchronize: true,
// migrations: ["/migrations/"],
// logger: "advanced-console"
// logging: "all",
cache: {
duration: 1000,
// alwaysEnabled: true,
type: 'ioredis',
options: {
port: Number(process.env.REDIS_PORT) || 6370, // Redis port
host: process.env.REDIS_HOST, // '127.0.0.1' Redis host
},
}, // 暂时缓存 10 秒 加入 全局缓存后暂时报错 需要做特殊处理
// cache: { duration: 1000, alwaysEnabled: true }, // 暂时缓存 60 秒 因为内存溢出暂时放弃
};
export const mysql_migration_config: DataSourceOptions = {
name: 'default',
type: 'mysql',
host: process.env.MYSQL_HOST,
port: Number(process.env.MYSQL_PORT),
username: process.env.MYSQL_USERNAME,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE,
entities: [join(__dirname, '../**/**.entity{.ts,.js}')],
migrations: [join(__dirname, '../../migrations/*.ts')],
};
export const db_config = mysql_config;
// 迁移使用的连接
const migrationDataSource = new DataSource(mysql_migration_config)
export default migrationDataSource

@ -0,0 +1,42 @@
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as Joi from 'joi';
import { configCheck } from './config.check';
export interface EnvConfig {
[key: string]: string;
}
export class ConfigService {
private readonly envConfig: { [key: string]: string };
constructor(filePath: string) {
const config = dotenv.parse(fs.readFileSync(filePath));
this.envConfig = this.validateInput(config);
}
get(key: string): string {
return this.envConfig[key];
}
get isApiAuthEnabled(): boolean {
return Boolean(this.envConfig.API_AUTH_ENABLED);
}
/**
* Ensures all needed variables are set, and returns the validated JavaScript object
* including the applied default values.
*/
private validateInput(envConfig: EnvConfig): EnvConfig {
const envVarsSchema: Joi.ObjectSchema = Joi.object(configCheck);
const { error, value: validatedEnvConfig } =envVarsSchema.validate(
this.envConfig
);
if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
return validatedEnvConfig;
}
}

@ -0,0 +1,17 @@
import { WeChatPayModuleAsyncOptions } from 'nest-wechatpay-node-v3'
import * as fs from 'fs'
// import { mongo_entities } from './config.entities'
// const API_STATUS = process.env.API_STATUS;
export const wechatpay_config: WeChatPayModuleAsyncOptions = {
useFactory: async () => {
return {
appid: process.env.WE_CHAT_PAY_APP_ID,
mchid: process.env.WE_CHAT_PAY_MCHID,
publicKey: fs.readFileSync('/Users/mac/foreign/群声/api/README.md'), // 公钥
privateKey: fs.readFileSync('/Users/mac/foreign/群声/api/README.md'), // 秘钥
}
}
};
export const db_config = wechatpay_config;

@ -0,0 +1,111 @@
import {
Controller,
Get,
UsePipes,
UseGuards,
Param,
Query,
Res,
UnauthorizedException,
Post,
UseInterceptors,
UploadedFile,
Body,
BadRequestException,
} from '@nestjs/common';
import { ApiBearerAuth, ApiTags, ApiOperation, ApiConsumes, ApiBody, ApiProperty } from '@nestjs/swagger';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AuthGuard } from '@nestjs/passport';
import { v4 as uuid } from 'uuid';
import { ValidationPipe, User } from '../common';
import { FileEntity } from './file.entity';
import redis from '../redis';
import { FileInterceptor } from '@nestjs/platform-express';
import path, { extname } from 'path';
import { diskStorage } from 'multer';
import fs from 'fs';
import { Base64File, FileType } from './file.params';
import { IsNotEmpty } from 'class-validator';
export class FileUploadType {
@ApiProperty({ type: 'string', format: 'binary' })
@IsNotEmpty()
file: any;
}
@ApiTags('file')
@ApiBearerAuth()
@Controller('file')
export class FileController {
constructor(
@InjectRepository(FileEntity)
private readonly fileRepository: Repository<FileEntity>,
) { }
@Post('upload/public')
@ApiOperation({ summary: '上传文件' })
@ApiConsumes('multipart/form-data')
@ApiBody({
description: 'file',
type: FileUploadType,
})
@UseGuards(AuthGuard('jwt'))
// @Roles('super_admin', '批量创建门锁')
@UseInterceptors(
FileInterceptor('file', {
storage: diskStorage({
destination: './upload/file',
filename: async (req, file, cb) => {
const randomName = Array(32)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('');
return cb(null, `${randomName}${extname(file.originalname)}`);
},
}),
}),
)
async uploadPublicFile(@UploadedFile() file, @Query() query: any, @Res() res, @User() viewer: any) {
// const filterType: string[] = ['.pdf', '.PDF', '.png', '.doc', '.docx', '.jpeg'];
const { filename } = file;
// const ext = extname(filename)
// 判断当前上传至接口的文件类型是否在白名单中,如果在则允许上传,不在则返回错误信息
// if (!filterType.includes(ext)) {
// return res.json({
// success: false,
// message: "文件格式错误,支持'.pdf'、'.PDF'、 '.png'、 '.doc'、 '.docx'、 '.jpeg'",
// });
// }
// const file_name = query.code + "-" + file.filename
return res.json({
message: 'ok',
success: true,
file_name: file.filename,
// uri: getDownloadUrl(file_name),
});
}
@Post('upload/base64')
@ApiOperation({ summary: '上传头像文件' })
@UseGuards(AuthGuard('jwt'))
@UsePipes(new ValidationPipe())
async louploadBase64Filegin(@Body() input: Base64File): Promise<any> {
const file_name = uuid() + '.png';
// if (!input.base64.startsWith('data:image/png;base64') && !input.base64.startsWith('data:image/jpeg;base64')) {
// throw new BadRequestException('文件类型不支持仅支持jpegpng')
// }
//过滤data:URL
const base64Data = input.base64.replace(/^data:image\/\w+;base64,/, '');
const dataBuffer = new Buffer(base64Data, 'base64');
// await uploadBase64File(file_name, dataBuffer);
return {
message: 'ok',
success: true,
file_name,
// uri: getDownloadUrl(file_name),
};
}
}

@ -0,0 +1,8 @@
import { Column, Entity } from 'typeorm';
import { Base } from '../common';
@Entity({ name: 'files' })
export class FileEntity extends Base {
@Column({ length: 50, nullable: false })
public name: string;
}

@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FileController } from './file.controller';
import { FileEntity } from './file.entity';
import { FileService } from './file.service';
import { ConfigModule } from '../config/config.module';
@Module({
imports: [TypeOrmModule.forFeature([FileEntity]), ConfigModule],
controllers: [FileController],
providers: [FileService],
exports: [FileService],
})
export class FileModule {}

@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty } from 'class-validator';
export class CreateFile {
@ApiProperty()
@IsString()
@IsNotEmpty()
public id: string;
}
export class Base64File {
@ApiProperty()
@IsString()
@IsNotEmpty()
public base64: string;
}
export class FileType {
@ApiProperty()
@IsString()
@IsNotEmpty()
public type: string;
}

@ -0,0 +1,18 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { FileEntity } from './file.entity';
@Injectable()
export class FileService {
constructor(
@InjectRepository(FileEntity)
private readonly fileRepository: Repository<FileEntity>,
) {}
async create(id: string): Promise<FileEntity> {
const new_file = Object.assign(new FileEntity(), { id });
const file = await this.fileRepository.save(new_file);
return file;
}
}

@ -0,0 +1,6 @@
import { List } from '../common';
import { FileEntity } from './file.entity';
export class FileList extends List {
list: FileEntity[];
}

@ -0,0 +1,62 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Logger } from '@nestjs/common';
// import { HttpExceptionFilter } from './common/HTTP-exception.filter';
import { options } from './config/config.doc'
import { SwaggerModule } from '@nestjs/swagger';
import { LoggingInterceptor } from './common/interceptor/logging.interceptor';
import * as fs from 'fs';
/**
*
200 OK - [GET]Idempotent
201 CREATED - [POST/PUT/PATCH]
202 Accepted - [*]
204 NO CONTENT - [DELETE]
400 INVALID REQUEST - [POST/PUT/PATCH]
401 Unauthorized - [*]
403 Forbidden - [*] 401访
404 NOT FOUND - [*]
406 Not Acceptable - [GET]JSONXML
410 Gone -[GET]
422 Unprocesable entity - [POST/PUT/PATCH]
500 INTERNAL SERVER ERROR - [*]
*/
async function bootstrap() {
console.log("当前运行环境", process.env.NODE_ENV)
const app = await NestFactory.create(AppModule);
app.enableCors({
origin(origin, cb) {
console.log('origin', origin);
if (process.env.NODE_ENV != "production") {
cb(null, true);
} else {
const CORS_ORIGIN = process.env.CORS_ORIGIN;
const whitelist = CORS_ORIGIN ? CORS_ORIGIN.split(',') : [];
cb(null, whitelist.includes(origin));
}
},
credentials: true,
});
// app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalInterceptors(new LoggingInterceptor());
if (process.env.DOC === 'true') {
const document = SwaggerModule.createDocument(app, options);
if (process.env.NODE_ENV === "development" || !process.env.NODE_ENV) {
fs.writeFileSync("./swagger-spec.json", JSON.stringify(document));
}
SwaggerModule.setup('doc', app, document); // 设置 doc 路由
}
// test_e();
// {
// EidToken: '095FA750-2234-4B1C-B395-22D2197657AB',
// RequestId: 'ce06f1e7-163a-40ab-90c5-fc227b3ecbd2',
// Url: 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxa50a8ac379585f53&redirect_uri=https%3A%2F%2Feid.faceid.qq.com%2Fapi%2Fv1%2FGetOpenId%3Ftoken%3D095FA750-2234-4B1C-B395-22D2197657AB%26v%3Dv2&response_type=code&scope=snsapi_base&state=&component_appid=wx9802ee81e68d6dee#wechat_redirect'
// }
const port: number = 9998
await app.listen(port);
Logger.log(`the server listing ${port}`)
}
bootstrap();

@ -0,0 +1,18 @@
import Redis from 'ioredis';
const config: any = {
port: Number(process.env.REDIS_PORT), // Redis port
host: process.env.REDIS_HOST, // '127.0.0.1' Redis host
// family: Number(process.env.REDIS_FAMILY) || 4, // 4 (IPv4) or 6 (IPv6)
// password: 'auth',
// db: Number(process.env.REDIS_DB) || 0,
// enableReadyCheck: true,
};
if (process.env.REDIS_PASSWORD) {
config.password = process.env.REDIS_PASSWORD;
}
const redis = new Redis(config);
export default redis;

@ -0,0 +1 @@
export class CreateRoleDto {}

@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateRoleDto } from './create-role.dto';
export class UpdateRoleDto extends PartialType(CreateRoleDto) {}

@ -0,0 +1 @@
export class Role {}

@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { RoleController } from './role.controller';
import { RoleService } from './role.service';
describe('RoleController', () => {
let controller: RoleController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [RoleController],
providers: [RoleService],
}).compile();
controller = module.get<RoleController>(RoleController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

@ -0,0 +1,36 @@
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { RoleService } from './role.service';
import { CreateRoleDto } from './dto/create-role.dto';
import { UpdateRoleDto } from './dto/update-role.dto';
import { ApiTags } from '@nestjs/swagger';
@ApiTags('用户角色')
@Controller('roles')
export class RoleController {
constructor(private readonly roleService: RoleService) {}
@Post()
create(@Body() createRoleDto: CreateRoleDto) {
return this.roleService.create(createRoleDto);
}
@Get()
findAll() {
return this.roleService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.roleService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateRoleDto: UpdateRoleDto) {
return this.roleService.update(+id, updateRoleDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.roleService.remove(+id);
}
}

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { RoleService } from './role.service';
import { RoleController } from './role.controller';
@Module({
controllers: [RoleController],
providers: [RoleService]
})
export class RoleModule {}

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { RoleService } from './role.service';
describe('RoleService', () => {
let service: RoleService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [RoleService],
}).compile();
service = module.get<RoleService>(RoleService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

@ -0,0 +1,26 @@
import { Injectable } from '@nestjs/common';
import { CreateRoleDto } from './dto/create-role.dto';
import { UpdateRoleDto } from './dto/update-role.dto';
@Injectable()
export class RoleService {
create(createRoleDto: CreateRoleDto) {
return 'This action adds a new role';
}
findAll() {
return `This action returns all role`;
}
findOne(id: number) {
return `This action returns a #${id} role`;
}
update(id: number, updateRoleDto: UpdateRoleDto) {
return `This action updates a #${id} role`;
}
remove(id: number) {
return `This action removes a #${id} role`;
}
}

@ -0,0 +1,17 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"
import { IsEnum, IsOptional, IsString } from "class-validator"
import { CommonPageArgs } from "src/common"
export class QueryUserDto extends CommonPageArgs {
@ApiPropertyOptional({ description: '真实姓名' })
@IsString()
@IsOptional()
true_name: string
@ApiPropertyOptional({ description: "根据名称模糊搜索", required: false })
@IsString()
@IsOptional()
search?: string
}

@ -0,0 +1,4 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"
import { IsEnum, IsOptional, IsString } from "class-validator"

@ -0,0 +1,49 @@
import {
Controller,
Get,
UsePipes,
UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { InjectRepository } from '@nestjs/typeorm';
import {
Repository,
} from 'typeorm';
import { AuthGuard } from '@nestjs/passport';
import {
ValidationPipe,
User,
Roles,
RolesGuard,
} from '../common';
import { UserEntity } from './user.entity';
import { UserService } from './user.service';
@ApiTags('系统用户')
@ApiBearerAuth()
@Controller('users')
export class UserController {
constructor(
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>,
private readonly userService: UserService
// @Inject(forwardRef(() => AuthService))
// private readonly authService: AuthService,
) { }
@Get('/viewer')
@ApiOperation({ summary: '获取个人信息' })
@UseGuards(AuthGuard('jwt'), RolesGuard)
@UsePipes(new ValidationPipe())
@Roles('company', 'tester', 'admin')
@ApiBearerAuth()
@ApiResponse({
status: 200,
description: '返回参数说明',
})
async viewer(@User() viewer: UserEntity) {
const result = await this.userRepository.findOne({ where: { id: viewer.id } })
return result;
}
}

@ -0,0 +1,24 @@
import { BaseEntity, Column, Entity, Index, JoinColumn, OneToMany, OneToOne } from 'typeorm';
import { createHmac } from 'crypto';
import { Base } from '../common';
import { ApiProperty } from '@nestjs/swagger';
// 用户表
@Entity({ name: 'users' })
export class UserEntity extends Base {
@ApiProperty({ description: '真实姓名' })
@Column({ length: 50, nullable: true, type: 'char' })
public true_name: string;
@ApiProperty({ description: '年龄' })
@Column({ nullable: true, type: 'int' })
public age: string;
@ApiProperty({ description: '手机号' })
@Column({ length: 50, nullable: true, type: 'char', select: false })
public mobile?: string;
@ApiProperty({ description: '用户头像' })
@Column({ nullable: true, length: 255, type: 'char' })
public avatar?: string;
}

@ -0,0 +1,23 @@
import { Module, forwardRef } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserService } from './user.service';
import { UserEntity } from './user.entity';
import { UserController } from './user.controller';
// import { AuthService } from '../auth/auth.service';
@Module({
imports: [
TypeOrmModule.forFeature([
UserEntity,
],
),
// TesterModule
// AuthModule,
// forwardRef(() => AuthModule),
],
providers: [UserService],
controllers: [UserController],
exports: [UserService],
})
export class UserModule { }

@ -0,0 +1,21 @@
import { Base } from '../common';
import { UserEntity } from './user.entity';
export class OtherUser extends Base {
public nickname?: string;
// mobile(@Root() { mobile }: UserEntity) {
// if (!mobile) {
// return mobile;
// }
// return mobile.substr(0, 3) + '****' + mobile.substr(7);
// }
// @Field(type => String, { nullable: true })
// true_name(@Root() { true_name }: UserEntity) {
// if (!true_name) {
// return true_name;
// }
// return true_name.replace(true_name.substr(0, 1), '*');
// }
}

@ -0,0 +1,5 @@
import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsNumber, IsEnum, IsBoolean } from 'class-validator';
import { CommonFilter } from '../common';

@ -0,0 +1,39 @@
import { BadRequestException, HttpException, HttpStatus, Inject, Injectable, Logger, forwardRef } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository, Like, DataSource } from 'typeorm';
import { UserEntity } from './user.entity';
import bcrypt from 'bcryptjs';
import * as bluebird from 'bluebird';
import { BCRYPT_HASH_ROUNDS } from '../common';
import { QueryUserDto } from './query-user.dto';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>,
private readonly dataSource: DataSource
) { }
findOneByToken(token: string) {
return false;
// return true;
}
findOneByName(username: string) {
// return this.userRepository.findOne({ where: { role } });
}
async findOneByIdNoRelation(id: string) {
return this.userRepository.createQueryBuilder('user')
.where(`user.id='${id}'`)
.getOne()
}
// 管理后台的成员查询
async findAllAndCount() {
const [list, count] = await this.userRepository.createQueryBuilder('user')
// .select(['id', 'nickName','team_id'])
// .addSelect('user.avatar')
.getManyAndCount()
return { list, count }
}
}

@ -0,0 +1,13 @@
import { Connection, List } from '../common';
import { UserEntity } from './user.entity';
export class UserList extends List {
list: UserEntity[];
}
export class ChartItem {
key: Date;
value: number;
}

@ -0,0 +1 @@
{"openapi":"3.0.0","paths":{"/":{"get":{"operationId":"AppController_getHello","parameters":[],"responses":{"200":{"description":""}}}},"/doc/json":{"get":{"operationId":"AppController_getDoc","parameters":[],"responses":{"200":{"description":""}}}},"/app.config":{"get":{"operationId":"AppController_appConfig","parameters":[],"responses":{"200":{"description":""}}}},"/auth/login/status/is_login":{"get":{"operationId":"AuthController_loginGetStatus","summary":"获取 token 是否失效","parameters":[{"name":"qunsense_code","required":true,"in":"query","description":"小程序码","schema":{"type":"string"}}],"responses":{"200":{"description":"返回参数说明","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginStatus"}}}}},"tags":["用户登录"]}},"/users/viewer":{"get":{"operationId":"UserController_viewer","summary":"获取个人信息","parameters":[],"responses":{"200":{"description":"返回参数说明"}},"tags":["系统用户"],"security":[{"bearer":[]},{"bearer":[]}]}},"/roles":{"post":{"operationId":"RoleController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRoleDto"}}}},"responses":{"201":{"description":""}},"tags":["用户角色"]},"get":{"operationId":"RoleController_findAll","parameters":[],"responses":{"200":{"description":""}},"tags":["用户角色"]}},"/roles/{id}":{"get":{"operationId":"RoleController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["用户角色"]},"patch":{"operationId":"RoleController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRoleDto"}}}},"responses":{"200":{"description":""}},"tags":["用户角色"]},"delete":{"operationId":"RoleController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["用户角色"]}},"/file/upload/public":{"post":{"operationId":"FileController_uploadPublicFile","summary":"上传文件","parameters":[],"requestBody":{"required":true,"description":"file","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/FileUploadType"}}}},"responses":{"201":{"description":""}},"tags":["file"],"security":[{"bearer":[]}]}},"/file/upload/base64":{"post":{"operationId":"FileController_louploadBase64Filegin","summary":"上传头像文件","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Base64File"}}}},"responses":{"201":{"description":""}},"tags":["file"],"security":[{"bearer":[]}]}}},"info":{"title":"restfull api","description":"\n Api为restfull风格api接口请求成功与否请使用header里面的status来判断200表示get请求成功201表示POST/PUT/PATCH请求成功 \n 204表示delete成功非上述code返回体会有message字段表示请求失败的错误描述例如header的status=500 那么返回体中回包含{message:\"Internal server error\"}\n 注意返回的message字段并不一定是string类型。注意orm使用的是固定版本typeorm@0.3.15。表结构存在关联字段更高版本每次都会重建主键id字段无论关联字段类型是uuid还是vachar(36)\n 重建时会删除id对应的值导致外建关联报错“ER_NO_REFERENCED_ROW2can not add or update a children row:xx\n ","version":"1.0","contact":{},"license":{"name":"2019 © shijianhuoban","url":"https://github.com/shijianhuoban"}},"tags":[],"servers":[],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"LoginStatus":{"type":"object","properties":{"status":{"type":"boolean","description":"登录状态返回"}}},"CreateRoleDto":{"type":"object","properties":{}},"UpdateRoleDto":{"type":"object","properties":{}},"FileUploadType":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]},"Base64File":{"type":"object","properties":{"base64":{"type":"string"}},"required":["base64"]}}}}

@ -0,0 +1,30 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as iosredis from 'ioredis'
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
import { AppService } from 'src/app.service';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
providers:[AppService]
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
// iosredis.disconnect()
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

@ -0,0 +1,12 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"moduleNameMapper": {
"^src/(.*)$": "<rootDir>/../src/$1"
}
}

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save