first commit
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',
|
||||
},
|
||||
};
|
||||
@ -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,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
|
||||
@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
||||
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,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,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,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,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,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
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue