diff --git a/.env.development b/.env.development index 4f2fa07..2d0e27c 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ MYSQL_HOST=localhost -MYSQL_PORT=33066 \ No newline at end of file +MYSQL_PORT=3306 \ No newline at end of file diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index c6e4dc7..9e9950a 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -22,6 +22,7 @@ jobs: id: docker_build uses: docker/build-push-action@v3 with: + platforms: linux/amd64,linux/arm64 push: true tags: eolinker/eoapi-remote-server:dev - name: Image digest diff --git a/.github/workflows/build-latest.yml b/.github/workflows/build-latest.yml index ec59f63..4c6aa75 100644 --- a/.github/workflows/build-latest.yml +++ b/.github/workflows/build-latest.yml @@ -1,9 +1,9 @@ name: Build Latest Image on: - release: - types: - - "published" + push: + tags: + - 'v*.*.*' env: VERSION: ${{ github.ref_name }} diff --git a/README.md b/README.md index e36191f..172107a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,17 @@ +此仓库已归档 + # eoapi-remote-server Eoapi 远程数据源后端服务,部署后即可通过公共数据源实现简单团队协作。 -如果你的数据不需要共享,也可以仅下载客户端单机使用。 +如果你的数据不需要共享,也可以仅下载 Eoapi 客户端单机使用。 ## 部署 -请访问 [部署文档](https://docs.eoapi.io/docs/storage.html) + +请访问 [部署文档](https://github.com/eolinker/eoapi-remote-server/wiki/%E4%BA%91%E7%AB%AF%E6%9C%8D%E5%8A%A1) + ## 开发 + Node.js 版本:^16 如果想提高开发效率,可以安装 NestJS 官方提供的命令行 nestjs/cli 快速生成组件、服务等模板。 @@ -24,10 +29,11 @@ docker-compose run -d --service-ports mysql 1. 安装依赖 ```bash -yarn +yarn ``` 2. 运行数据库迁移脚本 + ```bash yarn migration:run ``` @@ -40,8 +46,8 @@ yarn start:dev ### 运行 -| 命令 | 描述 | -| --------------- | ---------- | +| 命令 | 描述 | +| ------------------- | ---------- | | `npm run start:dev` | 运行服务器 | ### 更新数据库 diff --git a/docker-compose.yaml b/docker-compose.yaml index cf8c9a8..c5985e8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,7 +4,7 @@ services: eoapi-remote-server: # build: 从当前路径构建镜像 # build: . - image: eolinker/eoapi-remote-server:latest + image: eolinker/eoapi-remote-server:dev container_name: eoapi-remote-server deploy: restart_policy: @@ -35,7 +35,7 @@ services: - eoapi_net eoapi: - image: eolinker/eoapi:latest + image: eolinker/eoapi:dev container_name: eoapi deploy: restart_policy: @@ -51,7 +51,7 @@ services: - eoapi_net eoapi-test-server: - image: eolinker/eoapi-test-server:latest + image: eolinker/eoapi-test-server:dev container_name: eoapi-test-server deploy: restart_policy: diff --git a/package.json b/package.json index 9d6065e..91843fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eoapi-remote-server", - "version": "1.11.0", + "version": "2.0.0", "description": "Storage api data in remote server", "author": "eoapi", "private": true, @@ -13,9 +13,10 @@ "start:dev": "rimraf dist && cross-env NODE_ENV=development nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "cross-env NODE_ENV=production node dist/src/main.js", + "createDatabase": "node ./scripts/auto-create-database.js", "migration:create": "npx typeorm-ts-node-commonjs migration:create ./src/migrations/create-table", "migration:generate": "node ./scripts/migration-generate.js", - "migration:run": "npm run build&&npx typeorm-ts-node-commonjs migration:run -d ./src/config/data-source.ts", + "migration:run": "npm run createDatabase && npm run build&&npx typeorm-ts-node-commonjs migration:run -d ./src/config/data-source.ts", "migration:revert": "npx typeorm-ts-node-commonjs migration:revert -d ./src/config/data-source.ts", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "typeorm": "npx typeorm-ts-node-commonjs", @@ -46,6 +47,8 @@ "class-validator": "^0.13.2", "cross-env": "^7.0.3", "crypto-js": "^4.1.1", + "dayjs": "^1.11.7", + "lodash": "^4.17.21", "mysql2": "^2.3.3", "nanoid": "^3.3.4", "passport": "^0.6.0", @@ -64,6 +67,7 @@ "@types/crypto-js": "^4.1.1", "@types/express": "^4.17.14", "@types/jest": "29.2.2", + "@types/lodash": "^4.14.191", "@types/node": "^18.11.9", "@types/passport": "^1.0.11", "@types/passport-http": "^0.3.9", diff --git a/scripts/auto-create-database.js b/scripts/auto-create-database.js new file mode 100644 index 0000000..b33ecee --- /dev/null +++ b/scripts/auto-create-database.js @@ -0,0 +1,27 @@ +const mysql = require('mysql2'); +const dotenv = require('dotenv'); +dotenv.config(); + +const con = mysql.createConnection({ + host: process.env.MYSQL_HOST, + port: Number.parseInt(process.env.MYSQL_PORT, 10), + user: process.env.MYSQL_USERNAME, + password: process.env.MYSQL_PASSWORD || process.env.MYSQL_ROOT_PASSWORD, +}); + +con.connect((err) => { + if (err) throw err; + console.log('Connected!'); + + const sql = `CREATE DATABASE if not EXISTS ${process.env.MYSQL_DATABASE} CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci`; + + con.query(sql, (err) => { + if (err) { + console.log(err.sqlMessage); + } else { + console.log('Database created'); + } + + process.exit(); + }); +}); diff --git a/src/app.module.ts b/src/app.module.ts index ea4c0ad..46d3162 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; +import { APP_GUARD } from '@nestjs/core'; import { AppController } from './app.controller'; import { AppService } from './app.service'; // 引入数据库的及配置文件 @@ -10,6 +11,7 @@ import { getConfiguration } from './config/configuration'; import { UserModule } from '@/modules/user/user.module'; import { WorkspaceModule } from '@/modules/workspace/workspace.module'; import { SharedModule } from '@/shared/shared.module'; +import { JwtAuthGuard } from '@/guards'; console.log('process.env.NODE_ENV', `.env.${process.env.NODE_ENV}`); @Module({ @@ -33,7 +35,8 @@ console.log('process.env.NODE_ENV', `.env.${process.env.NODE_ENV}`); logging: configService.get('database.logging'), timezone: configService.get('database.timezone'), // 时区 }), - }), // 数据库 + }), + // 数据库 WorkspaceModule, SharedModule, AuthModule, // 认证 @@ -41,6 +44,12 @@ console.log('process.env.NODE_ENV', `.env.${process.env.NODE_ENV}`); ShareDocsModule, ], controllers: [AppController], - providers: [AppService], + providers: [ + AppService, + { + provide: APP_GUARD, + useClass: JwtAuthGuard, + }, + ], }) export class AppModule {} diff --git a/src/common/decorators/permission.decorator.ts b/src/common/decorators/permission.decorator.ts new file mode 100644 index 0000000..7855c34 --- /dev/null +++ b/src/common/decorators/permission.decorator.ts @@ -0,0 +1,6 @@ +import { SetMetadata } from '@nestjs/common'; +import { PermissionEnum } from '@/enums/permission.enum'; + +export const PERMISSIONS_KEY = 'permissions'; +export const Permissions = (permissions: PermissionEnum) => + SetMetadata(PERMISSIONS_KEY, permissions); diff --git a/src/config/data-source.ts b/src/config/data-source.ts index 9cfd932..8d2ddb3 100644 --- a/src/config/data-source.ts +++ b/src/config/data-source.ts @@ -1,6 +1,7 @@ import 'reflect-metadata'; import * as dotenv from 'dotenv'; import { DataSource } from 'typeorm'; + import { getConfiguration } from './configuration'; dotenv.config(); diff --git a/src/entities/base.new.entity.ts b/src/entities/base.new.entity.ts new file mode 100644 index 0000000..634eb5f --- /dev/null +++ b/src/entities/base.new.entity.ts @@ -0,0 +1,70 @@ +import { ApiHideProperty, ApiProperty } from '@nestjs/swagger'; +import { + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + DeleteDateColumn, + Generated, +} from 'typeorm'; +import { Exclude, Transform } from 'class-transformer'; +import dayjs from 'dayjs'; + +export class BaseTimestampEntity { + @ApiProperty({ type: Date, description: '创建时间' }) + @Transform(({ value }) => dayjs(value).format('YYYY-MM-DD HH:mm:ss'), { + toPlainOnly: true, + }) + @CreateDateColumn({ + type: 'datetime', + nullable: false, + name: 'created_at', + comment: '创建时间', + }) + createdAt: Date | null; + + @ApiProperty({ type: Date, description: '更新时间' }) + @UpdateDateColumn({ + type: 'datetime', + name: 'updated_at', + comment: '更新时间', + }) + @Transform(({ value }) => dayjs(value).format('YYYY-MM-DD HH:mm:ss'), { + toPlainOnly: true, + }) + updatedAt: Date | null; + + @Exclude() + @DeleteDateColumn({ + type: 'datetime', + name: 'deleted_at', + select: false, + comment: '删除时间', + }) + @Transform(({ value }) => dayjs(value).format('YYYY-MM-DD HH:mm:ss'), { + toPlainOnly: true, + }) + deletedAt: Date; +} + +export class BaseGeneratedEntity extends BaseTimestampEntity { + @ApiHideProperty() + @Exclude() + @PrimaryGeneratedColumn({ name: 'id', comment: '主键ID' }) + id: number; + + @ApiProperty({ type: String, description: 'uuid' }) + @Column({ comment: '业务 UUID' }) + @Generated('uuid') + uuid: string; +} + +export class BaseEntity extends BaseGeneratedEntity { + @ApiProperty({ type: Number, description: '创建者' }) + @Column('bigint', { name: 'create_by', nullable: true, comment: '创建者' }) + createBy: number | null; + + @ApiProperty({ type: Number, description: '更新者' }) + @Column('bigint', { name: 'update_by', nullable: true, comment: '更新者' }) + updateBy: number | null; +} diff --git a/src/entities/permission.entity.ts b/src/entities/permission.entity.ts new file mode 100644 index 0000000..2b338f5 --- /dev/null +++ b/src/entities/permission.entity.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Column, Entity } from 'typeorm'; +import { BaseEntity } from './base.new.entity'; + +@Entity({ name: 'permission' }) +export class PermissionEntity extends BaseEntity { + @ApiProperty({ type: String, description: '权限名称' }) + @Column({ type: 'varchar', unique: true, length: 120, comment: '权限名称' }) + name: string; + + @ApiProperty({ type: Number, description: '权限状态' }) + @Column({ + type: 'tinyint', + width: 1, + default: 1, + comment: '状态:0=禁用 1=启用', + }) + status: number; +} diff --git a/src/entities/project-user-role.entity.ts b/src/entities/project-user-role.entity.ts new file mode 100644 index 0000000..d67f9a7 --- /dev/null +++ b/src/entities/project-user-role.entity.ts @@ -0,0 +1,22 @@ +import { Column, Entity } from 'typeorm'; +import { BaseEntity } from './base.new.entity'; + +@Entity({ name: 'project_user_role' }) +export class ProjectUserRoleEntity extends BaseEntity { + @Column({ + name: 'project_id', + type: 'int', + comment: '项目ID', + }) + projectID: number; + + @Column({ + name: 'user_id', + type: 'int', + comment: '用户ID', + }) + userID: number; + + @Column({ name: 'role_id', type: 'int', comment: '角色ID' }) + roleID: number; +} diff --git a/src/entities/role-permission.entity.ts b/src/entities/role-permission.entity.ts new file mode 100644 index 0000000..185fe59 --- /dev/null +++ b/src/entities/role-permission.entity.ts @@ -0,0 +1,15 @@ +import { Column, Entity } from 'typeorm'; +import { BaseEntity } from './base.new.entity'; + +@Entity({ name: 'role_permission' }) +export class RolePermissionEntity extends BaseEntity { + @Column({ type: 'int', name: 'role_id', unsigned: true, comment: '角色ID' }) + roleID: number; + + @Column({ + type: 'int', + name: 'permission_id', + comment: '权限ID', + }) + permissionID: number; +} diff --git a/src/entities/role.entity.ts b/src/entities/role.entity.ts new file mode 100644 index 0000000..160ab3e --- /dev/null +++ b/src/entities/role.entity.ts @@ -0,0 +1,23 @@ +import { Column, Entity } from 'typeorm'; +import { BaseEntity } from './base.new.entity'; + +@Entity({ name: 'role' }) +export class RoleEntity extends BaseEntity { + @Column({ + type: 'varchar', + length: 50, + comment: '角色名称', + }) + name: string; + + @Column({ + type: 'tinyint', + width: 1, + unsigned: true, + comment: '角色类别: 1=空间 2=项目', + }) + module: number; + + @Column({ type: 'varchar', length: 200, default: '', comment: '角色备注' }) + remark: string; +} diff --git a/src/entities/workspace-user-role.entity.ts b/src/entities/workspace-user-role.entity.ts new file mode 100644 index 0000000..d891ce2 --- /dev/null +++ b/src/entities/workspace-user-role.entity.ts @@ -0,0 +1,22 @@ +import { Column, Entity } from 'typeorm'; +import { BaseEntity } from './base.new.entity'; + +@Entity({ name: 'workspace_user_role' }) +export class WorkspaceUserRoleEntity extends BaseEntity { + @Column({ + name: 'workspace_id', + type: 'int', + comment: '空间ID', + }) + workspaceID: number; + + @Column({ + name: 'user_id', + type: 'int', + comment: '用户ID', + }) + userID: number; + + @Column({ name: 'role_id', type: 'int', comment: '角色ID' }) + roleID: number; +} diff --git a/src/enums/permission.enum.ts b/src/enums/permission.enum.ts new file mode 100644 index 0000000..839713e --- /dev/null +++ b/src/enums/permission.enum.ts @@ -0,0 +1,48 @@ +export enum PermissionEnum { + /** workspace */ + UPDATE_WORKSPACE = 'update:workspace', + DELETE_WORKSPACE = 'delete:workspace', + VIEW_WORKSPACE = 'view:workspace', + ADD_WORKSPACE_MEMBER = 'add:workspace:member', + UPDATE_WORKSPACE_MEMBER = 'update:workspace:member', + DELETE_WORKSPACE__MEMBER = 'delete:workspace:member', + + /** project */ + VIEW_PROJECT_LIST = 'view:project:list', + VIEW_PROJECT = 'view:project', + UPDATE_PROJECT = 'update:project', + DELETE_PROJECT = 'delete:project', + IMPORT_PROJECT = 'import:project', + EXPORT_PROJECT = 'export:project', + ADD_PROJECT_MEMBER = 'add:project:member', + UPDATE_PROJECT_MEMBER = 'update:project:member', + DELETE_PROJECT__MEMBER = 'delete:project:member', + + /** apiGroup */ + VIEW_API_GROUP = 'view:apiGroup', + CREATE_API_GROUP = 'create:apiGroup', + UPDATE_API_GROUP = 'update:apiGroup', + DELETE_API_GROUP = 'delete:apiGroup', + + /** apiData */ + VIEW_API_DATA = 'view:apiData', + CREATE_API_DATA = 'create:apiData', + UPDATE_API_DATA = 'update:apiData', + DELETE_API_DATA = 'delete:apiData', + + /** environment */ + VIEW_ENVIRONMENT = 'view:environment', + CREATE_ENVIRONMENT = 'create:environment', + UPDATE_ENVIRONMENT = 'update:environment', + DELETE_ENVIRONMENT = 'delete:environment', + + /** apiTestHistory */ + VIEW_API_TEST_HISTORY = 'view:apiTestHistory', + CREATE_API_TEST_HISTORY = 'create:apiTestHistory', + DELETE_API_TEST_HISTORY = 'delete:apiTestHistory', + + /** shared */ + CREATE_SHARED = 'create:shared', + VIEW_SHARED = 'view:shared', + DELETE_SHARED = 'delete:shared', +} diff --git a/src/enums/role.enum.ts b/src/enums/role.enum.ts new file mode 100644 index 0000000..5621da3 --- /dev/null +++ b/src/enums/role.enum.ts @@ -0,0 +1,6 @@ +export enum RoleEnum { + WorkspaceOwnerRoleID = 1, + WorkspaceEditorRoleID = 2, + ProjectOwnerRoleID = 3, + ProjectEditorRoleID = 4, +} diff --git a/src/guards/index.ts b/src/guards/index.ts new file mode 100644 index 0000000..e174be2 --- /dev/null +++ b/src/guards/index.ts @@ -0,0 +1,2 @@ +export * from './jwt-auth.guard'; +export * from './roles.guard'; diff --git a/src/modules/auth/guards/jwt-auth.guard.ts b/src/guards/jwt-auth.guard.ts similarity index 75% rename from src/modules/auth/guards/jwt-auth.guard.ts rename to src/guards/jwt-auth.guard.ts index 6251c1b..49d4d25 100644 --- a/src/modules/auth/guards/jwt-auth.guard.ts +++ b/src/guards/jwt-auth.guard.ts @@ -7,12 +7,14 @@ import { } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { JwtService } from '@nestjs/jwt'; +import { ConfigService } from '@nestjs/config'; import { isEmpty } from 'lodash'; import { IS_PUBLIC_KEY } from '@/common/decorators/public.decorator'; import { AuthService } from '@/modules/auth/auth.service'; import { IUser } from '@/common/decorators/user.decorator'; import { UserService } from '@/modules/user/user.service'; import { ProjectService } from '@/modules/workspace/project/project.service'; +import { WorkspaceService } from '@/modules/workspace/workspace.service'; /** * admin perm check guard @@ -25,6 +27,8 @@ export class JwtAuthGuard implements CanActivate { private authService: AuthService, private userService: UserService, private projectService: ProjectService, + private workspaceService: WorkspaceService, + private readonly config: ConfigService, ) {} async canActivate(context: ExecutionContext): Promise { @@ -43,17 +47,19 @@ export class JwtAuthGuard implements CanActivate { } try { // 挂载对象到当前请求上 - request.currentUser = this.jwtService.verify(token); + request.currentUser = this.jwtService.verify(token, { + secret: this.config.get('jwt.secret'), + }); const isExit = await this.authService.findOne({ accessToken: token }); - const { passwordVersion } = await this.userService.findOne({ + const user = await this.userService.findOne({ where: { id: request.currentUser.userId }, select: ['passwordVersion'], }); - if (!isExit || passwordVersion !== request.currentUser.pv) { + if (!isExit || user?.passwordVersion !== request.currentUser.pv) { throw new UnauthorizedException('您的密码已更新,请重新登录'); } } catch (e) { - console.log('e', e); + console.log('JwtAuthGuard e', e); // 无法通过token校验 throw new UnauthorizedException('token已失效,请重新登录'); } @@ -61,34 +67,28 @@ export class JwtAuthGuard implements CanActivate { throw new UnauthorizedException('当前用户不存在'); } + const userID = request.currentUser.userId; + const workspaceID = Number( request?.params?.workspaceID || request.headers['x-workspace-id'], ); const projectID = Number( request?.params?.projectID || request.headers['x-project-id'], ); - if (!Number.isNaN(workspaceID)) { - const hasWorkspaceAuth = await this.userService.findOneBy({ - id: request.currentUser.userId, - workspaces: { - id: workspaceID, - }, - ...(Number.isNaN(projectID) - ? {} - : { - projects: { - uuid: projectID, - }, - }), - }); + if (Number(workspaceID) > 0) { + const hasWorkspaceAuth = await this.workspaceService.hasWorkspaceAuth( + workspaceID, + userID, + ); if (!hasWorkspaceAuth) { throw new ForbiddenException('没有该空间访问权限'); } } - if (!Number.isNaN(projectID)) { - const hasProjectAuth = await this.projectService.findOne( + if (Number(projectID) > 0) { + const hasProjectAuth = await this.projectService.hasProjectAuth( workspaceID, projectID, + userID, ); if (!hasProjectAuth) { throw new ForbiddenException('没有该项目访问权限'); diff --git a/src/modules/auth/strategies/jwt.strategy.ts b/src/guards/jwt.strategy.ts similarity index 100% rename from src/modules/auth/strategies/jwt.strategy.ts rename to src/guards/jwt.strategy.ts diff --git a/src/guards/roles.guard.ts b/src/guards/roles.guard.ts new file mode 100644 index 0000000..a1e6516 --- /dev/null +++ b/src/guards/roles.guard.ts @@ -0,0 +1,36 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { PERMISSIONS_KEY } from '@/common/decorators/permission.decorator'; +import { PermissionEnum } from '@/enums/permission.enum'; +import { ProjectService } from '@/modules/workspace/project/project.service'; +import { WorkspaceService } from '@/modules/workspace/workspace.service'; + +@Injectable() +export class RolesGuard implements CanActivate { + constructor( + private reflector: Reflector, + private projectService: ProjectService, + private workspaceService: WorkspaceService, + ) {} + + canActivate(context: ExecutionContext): boolean { + // 当前请求所需权限 + const currentPerm = this.reflector.getAllAndOverride( + PERMISSIONS_KEY, + [context.getHandler(), context.getClass()], + ); + // 当前用户 + const { currentUser, params = {} } = context.switchToHttp().getRequest(); + const userID = currentUser.userId; + // console.log('currentPerm', currentPerm, currentUser, params); + + if (!currentPerm) { + return true; + } + + // const rolePermission = this.workspaceService.getRolePermission(userID); + + return true; + // return requiredRoles.some((role) => user.roles?.includes(role)); + } +} diff --git a/src/migrations/1671766182451-update-table_1_11_0.ts b/src/migrations/1671766182451-update-table_1_11_0.ts new file mode 100644 index 0000000..1e524dc --- /dev/null +++ b/src/migrations/1671766182451-update-table_1_11_0.ts @@ -0,0 +1,157 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import { PermissionEntity } from '@/entities/permission.entity'; +import { RoleEntity } from '@/entities/role.entity'; +import { PermissionEnum } from '@/enums/permission.enum'; +import { RolePermissionEntity } from '@/entities/role-permission.entity'; + +const perms = Object.values(PermissionEnum); + +const workspaceOwnerPerms = [ + 'update:workspace', + 'delete:workspace', + 'view:workspace', + 'add:workspace:member', + 'update:workspace:member', + 'delete:workspace:member', + 'view:project:list', + 'view:project', + 'update:project', + 'delete:project', + 'add:project:member', + 'update:project:member', + 'delete:project:member', +]; + +export class updateTable11101671725826115 implements MigrationInterface { + name = 'updateTable11101671725826115'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE \`permission\` (\`created_at\` datetime(6) NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL COMMENT '删除时间', \`id\` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', \`uuid\` varchar(36) NOT NULL COMMENT '业务 UUID', \`create_by\` bigint NULL COMMENT '创建者', \`update_by\` bigint NULL COMMENT '更新者', \`name\` varchar(120) NOT NULL COMMENT '权限名称', \`status\` tinyint(1) NOT NULL COMMENT '状态:0=禁用 1=启用' DEFAULT '1', UNIQUE INDEX \`IDX_240853a0c3353c25fb12434ad3\` (\`name\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`role_permission\` (\`created_at\` datetime(6) NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL COMMENT '删除时间', \`id\` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', \`uuid\` varchar(36) NOT NULL COMMENT '业务 UUID', \`create_by\` bigint NULL COMMENT '创建者', \`update_by\` bigint NULL COMMENT '更新者', \`role_id\` int UNSIGNED NOT NULL COMMENT '角色ID', \`permission_id\` int NOT NULL COMMENT '权限ID', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`project_user_role\` (\`created_at\` datetime(6) NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL COMMENT '删除时间', \`id\` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', \`uuid\` varchar(36) NOT NULL COMMENT '业务 UUID', \`create_by\` bigint NULL COMMENT '创建者', \`update_by\` bigint NULL COMMENT '更新者', \`project_id\` int NOT NULL COMMENT '项目ID', \`user_id\` int NOT NULL COMMENT '用户ID', \`role_id\` int NOT NULL COMMENT '角色ID', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`workspace_user_role\` (\`created_at\` datetime(6) NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL COMMENT '删除时间', \`id\` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', \`uuid\` varchar(36) NOT NULL COMMENT '业务 UUID', \`create_by\` bigint NULL COMMENT '创建者', \`update_by\` bigint NULL COMMENT '更新者', \`workspace_id\` int NOT NULL COMMENT '空间ID', \`user_id\` int NOT NULL COMMENT '用户ID', \`role_id\` int NOT NULL COMMENT '角色ID', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`role\` (\`created_at\` datetime(6) NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL COMMENT '删除时间', \`id\` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', \`uuid\` varchar(36) NOT NULL COMMENT '业务 UUID', \`create_by\` bigint NULL COMMENT '创建者', \`update_by\` bigint NULL COMMENT '更新者', \`name\` varchar(50) NOT NULL COMMENT '角色名称', \`module\` tinyint(1) UNSIGNED NOT NULL COMMENT '角色类别: 1=空间 2=项目', \`remark\` varchar(200) NOT NULL COMMENT '角色备注' DEFAULT '', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + + // 初始化权限 + await queryRunner.manager.insert( + 'permission', + perms.map((name) => ({ name })), + ); + + // 初始化默认角色 + const roleInsertResult = await queryRunner.manager.insert( + 'role', + [ + { + name: 'Owner', + module: 1, + remark: 'workspace owner', + }, + { + name: 'Editor', + module: 1, + remark: 'workspace editor', + }, + { + name: 'Owner', + module: 2, + remark: 'project owner', + }, + { + name: 'Editor', + module: 2, + remark: 'project editor', + }, + ], + ); + + const [wOwner, wEditor, pOwner, pEditor] = roleInsertResult.identifiers; + + for (const wPerm of workspaceOwnerPerms) { + const perm = await queryRunner.manager.findOneBy( + 'permission', + { name: wPerm }, + ); + await queryRunner.manager.insert( + 'role_permission', + { + roleID: wOwner.id, + permissionID: perm.id, + }, + ); + // workspace editor 没有删除或移除操作 + if ( + ![ + 'delete', + 'remove', + 'add:workspace:member', + 'update:workspace:member', + ].some((n) => perm.name.includes(n)) + ) { + await queryRunner.manager.insert( + 'role_permission', + { + roleID: wEditor.id, + permissionID: perm.id, + }, + ); + } + } + + for (const name of perms) { + if (name.includes('workspace')) { + continue; + } + const perm = await queryRunner.manager.findOneBy( + 'permission', + { name }, + ); + await queryRunner.manager.insert( + 'role_permission', + { + roleID: pOwner.id, + permissionID: perm.id, + }, + ); + // project editor 没有删除或更新项目操作 + if ( + ![ + 'update:project', + 'delete:project', + 'add:project:member', + 'update:project:member', + 'delete:project:member', + ].some((n) => n === name) + ) { + await queryRunner.manager.insert( + 'role_permission', + { + roleID: pEditor.id, + permissionID: perm.id, + }, + ); + } + } + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE \`role\``); + await queryRunner.query(`DROP TABLE \`workspace_user_role\``); + await queryRunner.query(`DROP TABLE \`project_user_role\``); + await queryRunner.query(`DROP TABLE \`role_permission\``); + await queryRunner.query( + `DROP INDEX \`IDX_240853a0c3353c25fb12434ad3\` ON \`permission\``, + ); + await queryRunner.query(`DROP TABLE \`permission\``); + } +} diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index b9cf618..df3c55e 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -1,13 +1,10 @@ import { Module } from '@nestjs/common'; import { PassportModule } from '@nestjs/passport'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { JwtModule } from '@nestjs/jwt'; -import { APP_GUARD } from '@nestjs/core'; +import { ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AuthController } from './auth.controller'; import { UserModule } from '@/modules/user/user.module'; import { AuthService } from '@/modules/auth/auth.service'; -import { JwtAuthGuard } from '@/modules/auth/guards/jwt-auth.guard'; import { AuthEntity } from '@/entities/auth.entity'; import { WorkspaceModule } from '@/modules/workspace/workspace.module'; @@ -18,25 +15,9 @@ import { WorkspaceModule } from '@/modules/workspace/workspace.module'; ConfigModule, UserModule, WorkspaceModule, - PassportModule, - JwtModule.registerAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - secret: configService.get('jwt.secret'), - signOptions: { expiresIn: '600s' }, - }), - inject: [ConfigService], - }), ], controllers: [AuthController], - providers: [ - AuthService, - // JwtStrategy, - { - provide: APP_GUARD, - useClass: JwtAuthGuard, - }, - ], + providers: [AuthService], exports: [AuthService], }) export class AuthModule {} diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 714d394..0fd1df1 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -1,6 +1,4 @@ import { - forwardRef, - Inject, Injectable, OnModuleInit, UnauthorizedException, @@ -8,11 +6,11 @@ import { import { JwtService } from '@nestjs/jwt'; import { InjectRepository } from '@nestjs/typeorm'; import { ModuleRef } from '@nestjs/core'; +import { ConfigService } from '@nestjs/config'; import { DeleteResult, FindOptionsWhere, Repository } from 'typeorm'; import { nanoid } from 'nanoid'; import { LoginInfoDto } from '@/modules/auth/dto/login.dto'; import { UserService } from '@/modules/user/user.service'; -import { UserEntity } from '@/entities/user.entity'; import { AuthEntity } from '@/entities/auth.entity'; import { accessTokenExpiresIn, @@ -29,6 +27,7 @@ export class AuthService implements OnModuleInit { private readonly authEntityRepository: Repository, private readonly jwtService: JwtService, private moduleRef: ModuleRef, + private readonly config: ConfigService, ) {} onModuleInit() { this.userService = this.moduleRef.get(UserService, { strict: false }); @@ -105,7 +104,10 @@ export class AuthService implements OnModuleInit { const result = { accessToken: this.jwtService.sign( { userId: userEntity.id, pv: userEntity.passwordVersion }, - { expiresIn: accessTokenExpiresIn / 1000 }, + { + expiresIn: accessTokenExpiresIn / 1000, + secret: this.config.get('jwt.secret'), + }, ), refreshToken: nanoid(), accessTokenExpiresAt: date.getTime() + accessTokenExpiresIn, diff --git a/src/modules/user/user.dto.ts b/src/modules/user/user.dto.ts index 000a089..2288015 100644 --- a/src/modules/user/user.dto.ts +++ b/src/modules/user/user.dto.ts @@ -26,11 +26,6 @@ export class UpdateUserInfoDto extends PartialType( ) {} export class UpdateUserPasswordDto { - @ApiProperty({ description: '旧密码', required: false }) - @MinLength(6) - @IsString() - readonly oldPassword: string; - @ApiProperty({ description: '新密码', required: false }) @MinLength(6) @IsString() @@ -38,11 +33,6 @@ export class UpdateUserPasswordDto { } export class UserInfoValidator { - @ApiProperty({ description: '旧密码', required: false }) - @MinLength(6) - @IsString() - readonly oldPassword: string; - @ApiProperty({ description: '新密码', required: false }) @MinLength(6) @IsString() diff --git a/src/modules/user/user.module.ts b/src/modules/user/user.module.ts index 5dcf692..f2ae35a 100644 --- a/src/modules/user/user.module.ts +++ b/src/modules/user/user.module.ts @@ -3,9 +3,17 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { UserController } from './user.controller'; import { UserService } from './user.service'; import { UserEntity } from '@/entities/user.entity'; +import { WorkspaceUserRoleEntity } from '@/entities/workspace-user-role.entity'; +import { ProjectUserRoleEntity } from '@/entities/project-user-role.entity'; @Module({ - imports: [TypeOrmModule.forFeature([UserEntity])], + imports: [ + TypeOrmModule.forFeature([ + UserEntity, + WorkspaceUserRoleEntity, + ProjectUserRoleEntity, + ]), + ], controllers: [UserController], providers: [UserService], exports: [UserService], diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts index 39c767f..322dfbe 100644 --- a/src/modules/user/user.service.ts +++ b/src/modules/user/user.service.ts @@ -86,7 +86,6 @@ export class UserService implements OnModuleInit { validateUser(userDto: Partial) { const userValidator = new UserEntity(); userValidator.email = userDto.username; - userValidator.mobilePhone = userDto.username; return validate(userValidator).then((errors) => { const result = {} as UserEntity; @@ -94,9 +93,6 @@ export class UserService implements OnModuleInit { if (!validateFields.includes('email')) { result.email = userDto.username; } - if (!validateFields.includes('mobilePhone')) { - result.mobilePhone = userDto.username; - } return result; }); } @@ -122,7 +118,7 @@ export class UserService implements OnModuleInit { } else { const other = await this.validateUser(userDto); if (Object.keys(other).length === 0) { - throw new Error('用户名必须是手机号码或邮箱'); + throw new Error('用户名必须是邮箱'); } const user = await this.userRepository.save({ ...other, @@ -130,18 +126,6 @@ export class UserService implements OnModuleInit { password: this.utils.md5(userDto.password), }); this.userRepository.update(user.id, { username: `user_${nanoid(10)}` }); - if (user.id === 1) { - const defaultProject = await this.projectService.findOneBy(1); - if (defaultProject) { - this.workspaceService.create( - user.id, - { - title: '默认空间', - }, - defaultProject, - ); - } - } return { ...user, @@ -165,10 +149,6 @@ export class UserService implements OnModuleInit { userId, userInfoDto: UpdateUserInfoDto, ): Promise { - // const other = await this.validateUser(userInfoDto); - // if (userInfoDto.username && Object.keys(other).length === 0) { - // throw new Error('用户名必须是手机号码或邮箱'); - // } const isConflict = await this.userRepository.findOne({ where: { username: Equal(userInfoDto.username), id: Not(userId) }, }); @@ -196,10 +176,10 @@ export class UserService implements OnModuleInit { if (!userPasswordDto.newPassword) { throw new ConflictException('新密码不能为空'); } - const oldPassword = this.utils.md5(userPasswordDto.oldPassword); - if (oldPassword !== user.password) { - throw new ForbiddenException('旧密码验证失败'); - } + // const oldPassword = this.utils.md5(userPasswordDto.oldPassword); + // if (oldPassword !== user.password) { + // throw new ForbiddenException('旧密码验证失败'); + // } await this.userRepository.update(userId, { password: this.utils.md5(userPasswordDto.newPassword), passwordVersion: user.passwordVersion + 1, diff --git a/src/modules/workspace/apiData/apiData.controller.ts b/src/modules/workspace/apiData/apiData.controller.ts index ff64496..73e516f 100644 --- a/src/modules/workspace/apiData/apiData.controller.ts +++ b/src/modules/workspace/apiData/apiData.controller.ts @@ -7,6 +7,7 @@ import { Param, Delete, Query, + UseGuards, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { ValidateQueryPipe } from 'src/pipe/query.pipe'; @@ -21,9 +22,11 @@ import { ApiOkResponseData, } from '@/common/class/res.class'; import { ApiData } from '@/entities/apiData.entity'; +import { RolesGuard } from '@/guards'; @ApiTags('apiData') @Controller(`${WORKSPACE_PROJECT_PREFIX}/api_data`) +@UseGuards(RolesGuard) export class ApiDataController { private readonly JSON_FIELDS = [ 'requestHeaders', diff --git a/src/modules/workspace/apiData/samples/sample.api.data.ts b/src/modules/workspace/apiData/samples/sample.api.data.ts index c6b1d09..a812c2d 100644 --- a/src/modules/workspace/apiData/samples/sample.api.data.ts +++ b/src/modules/workspace/apiData/samples/sample.api.data.ts @@ -10,7 +10,7 @@ export const sampleApiData = [ method: 'GET', requestBodyType: 'raw', requestBodyJsonType: 'object', - requestBody: {}, + requestBody: [], queryParams: [], restParams: [ { @@ -110,7 +110,7 @@ export const sampleApiData = [ method: 'GET', requestBodyType: 'raw', requestBodyJsonType: 'object', - requestBody: {}, + requestBody: [], queryParams: [{ name: 'name', required: true, example: 'disease_h5' }], restParams: [], requestHeaders: [], diff --git a/src/modules/workspace/apiGroup/apiGroup.controller.ts b/src/modules/workspace/apiGroup/apiGroup.controller.ts index 206fcce..d462e58 100644 --- a/src/modules/workspace/apiGroup/apiGroup.controller.ts +++ b/src/modules/workspace/apiGroup/apiGroup.controller.ts @@ -10,6 +10,7 @@ import { ParseIntPipe, NotFoundException, BadRequestException, + UseGuards, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { ValidateQueryPipe } from 'src/pipe/query.pipe'; @@ -23,9 +24,11 @@ import { ApiOkResponseData, } from '@/common/class/res.class'; import { ApiGroup } from '@/entities/apiGroup.entity'; +import { RolesGuard } from '@/guards'; @ApiTags('apiGroup') @Controller(`${WORKSPACE_PROJECT_PREFIX}/group`) +@UseGuards(RolesGuard) export class ApiGroupController { constructor(private readonly service: ApiGroupService) {} diff --git a/src/modules/workspace/apiTestHistory/apiTestHistory.controller.ts b/src/modules/workspace/apiTestHistory/apiTestHistory.controller.ts index d89225d..b6a4f25 100644 --- a/src/modules/workspace/apiTestHistory/apiTestHistory.controller.ts +++ b/src/modules/workspace/apiTestHistory/apiTestHistory.controller.ts @@ -7,6 +7,7 @@ import { Param, Delete, Query, + UseGuards, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { ValidateQueryPipe } from 'src/pipe/query.pipe'; @@ -21,9 +22,11 @@ import { ApiOkResponseData, } from '@/common/class/res.class'; import { ApiTestHistory } from '@/entities/apiTestHistory.entity'; +import { RolesGuard } from '@/guards'; @ApiTags('apiTestHistory') @Controller(`${WORKSPACE_PROJECT_PREFIX}/api_test_history`) +@UseGuards(RolesGuard) export class ApiTestHistoryController { private readonly JSON_FIELDS = ['general', 'request', 'response']; diff --git a/src/modules/workspace/environment/environment.controller.ts b/src/modules/workspace/environment/environment.controller.ts index 8209849..724c63b 100644 --- a/src/modules/workspace/environment/environment.controller.ts +++ b/src/modules/workspace/environment/environment.controller.ts @@ -7,6 +7,7 @@ import { Param, Delete, Query, + UseGuards, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { EnvironmentService } from './environment.service'; @@ -19,9 +20,11 @@ import { ApiOkResponseData, } from '@/common/class/res.class'; import { Environment } from '@/entities/environment.entity'; +import { RolesGuard } from '@/guards'; @ApiTags('Environment') @Controller(`${WORKSPACE_PROJECT_PREFIX}/environment`) +@UseGuards(RolesGuard) export class EnvironmentController { private readonly NOT_FOUND = { statusCode: 201, diff --git a/src/modules/workspace/project/dto/export.dto.ts b/src/modules/workspace/project/dto/export.dto.ts index ec98b64..6d0075b 100644 --- a/src/modules/workspace/project/dto/export.dto.ts +++ b/src/modules/workspace/project/dto/export.dto.ts @@ -1,17 +1,6 @@ -import { Child, Environment as EnvironmentType } from './import.dto'; -import { ApiData } from '@/entities/apiData.entity'; -import { ApiGroup } from '@/entities/apiGroup.entity'; -import { Environment } from '@/entities/environment.entity'; -import { Project } from '@/entities/project.entity'; - -export class ExportCollectionsResultDto { - collections: Child; - enviroments: EnvironmentType; -} +import { Child, Environment } from './import.dto'; export class ExportProjectResultDto { - environment: Environment[]; - group: ApiGroup[]; - project: Project; - apiData: ApiData[]; + collections: Child[]; + environments: Environment[]; } diff --git a/src/modules/workspace/project/dto/import.dto.ts b/src/modules/workspace/project/dto/import.dto.ts index 181bf96..e73e7fd 100644 --- a/src/modules/workspace/project/dto/import.dto.ts +++ b/src/modules/workspace/project/dto/import.dto.ts @@ -1,7 +1,7 @@ export class ImportDto { groupID: number; collections: Child[]; - enviroments: Environment[]; + environments: Environment[]; } export type Child = @@ -30,7 +30,7 @@ export type Environment = { export type Collections = { collections: Child[]; - enviroments: Environment[]; + environments: Environment[]; }; export interface ParamsEnum { @@ -287,14 +287,14 @@ export interface ApiData { } export type ImportResult = { - errors: { + error: { apiData: any[]; group: any[]; - enviroments: any[]; + environments: any[]; }; successes: { apiData: any[]; group: any[]; - enviroments: any[]; + environments: any[]; }; }; diff --git a/src/modules/workspace/project/dto/update.dto.ts b/src/modules/workspace/project/dto/update.dto.ts index 20d53c8..07fb6a3 100644 --- a/src/modules/workspace/project/dto/update.dto.ts +++ b/src/modules/workspace/project/dto/update.dto.ts @@ -1,4 +1,15 @@ import { PartialType } from '@nestjs/mapped-types'; +import { IsInt, Max, Min } from 'class-validator'; import { CreateDto } from './create.dto'; +import { RoleEnum } from '@/enums/role.enum'; export class UpdateDto extends PartialType(CreateDto) {} + +export class SetRoleDto { + @IsInt() + @Min(RoleEnum.ProjectOwnerRoleID) + @Max(RoleEnum.ProjectEditorRoleID) + roleID: number; + + memberID: number; +} diff --git a/src/modules/workspace/project/project.controller.ts b/src/modules/workspace/project/project.controller.ts index a5d064d..c570319 100644 --- a/src/modules/workspace/project/project.controller.ts +++ b/src/modules/workspace/project/project.controller.ts @@ -9,11 +9,12 @@ import { Query, ParseIntPipe, NotFoundException, + UseGuards, } from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ProjectService } from './project.service'; import { CreateDto } from './dto/create.dto'; -import { UpdateDto } from './dto/update.dto'; +import { SetRoleDto, UpdateDto } from './dto/update.dto'; import { CollectionsDto, QueryDto } from './dto/query.dto'; import { ImportDto } from './dto/import.dto'; import { WORKSPACE_ID_PREFIX } from '@/common/contants/prefix.contants'; @@ -22,24 +23,35 @@ import { ApiOkResponseData, } from '@/common/class/res.class'; import { Project } from '@/entities/project.entity'; +import { ExportProjectResultDto } from '@/modules/workspace/project/dto/export.dto'; +import { RolesGuard } from '@/guards'; +import { IUser, User } from '@/common/decorators/user.decorator'; import { - ExportProjectResultDto, - ExportCollectionsResultDto, -} from '@/modules/workspace/project/dto/export.dto'; + RolePermissionDto, + WorkspaceMemberAddDto, + WorkspaceMemberRemoveDto, + WorkspaceUser, +} from '@/modules/workspace/workspace.dto'; +import { RoleEntity } from '@/entities/role.entity'; +import { UserEntity } from '@/entities/user.entity'; +import { Permissions } from '@/common/decorators/permission.decorator'; +import { PermissionEnum } from '@/enums/permission.enum'; @ApiTags('Project') @Controller(`${WORKSPACE_ID_PREFIX}/project`) +@UseGuards(RolesGuard) export class ProjectController { constructor(private readonly service: ProjectService) {} @ApiCreatedResponseData(Project) @Post() async create( + @User() user: IUser, @Param('workspaceID', ParseIntPipe) workspaceID, @Body() createDto: CreateDto, ) { - const data = await this.service.create(createDto); - return this.findOne(workspaceID, `${data.uuid}`); + const data = await this.service.create(createDto, workspaceID, user.userId); + return await this.findOne(data.uuid, workspaceID); } @ApiCreatedResponseData() @@ -57,14 +69,17 @@ export class ProjectController { return this.service.findAll(query, workspaceID); } + @Permissions(PermissionEnum.VIEW_PROJECT) @ApiOkResponseData(Project) @Get(':projectID') async findOne( @Param('projectID', ParseIntPipe) projectID, @Param('workspaceID', ParseIntPipe) workspaceID, ) { - return this.service.findOne(workspaceID, projectID); + return this.service.findOne(projectID, workspaceID); } + + @Permissions(PermissionEnum.UPDATE_PROJECT) @ApiOkResponseData(Project) @Put(':projectID') async update( @@ -74,11 +89,13 @@ export class ProjectController { ) { const data = await this.service.update(+projectID, updateDto); if (data) { - return await this.findOne(workspaceID, projectID); + return await this.findOne(projectID, workspaceID); } return new NotFoundException('更新失败!项目不存在'); } + + @Permissions(PermissionEnum.DELETE_PROJECT) @ApiOkResponseData() @Delete(':projectID') async remove(@Param('projectID') projectID: string) { @@ -101,21 +118,11 @@ export class ProjectController { return this.service.import(workspaceID, Number(projectID), importDto); } - @ApiOkResponseData(ExportCollectionsResultDto) - @Get(':projectID/export/collections') - async export( - @Param('projectID') projectID: string, - @Param('workspaceID', ParseIntPipe) workspaceID, - ) { - // console.log('projectID', projectID, importDto); - return this.service.exportCollections(workspaceID, Number(projectID)); - } - @ApiOkResponseData(ExportProjectResultDto) @Get(':projectID/export') async projectExport(@Param('projectID') projectID: string) { // console.log('projectID', projectID, importDto); - return this.service.projectExport(Number(projectID)); + return this.service.exportCollections(Number(projectID)); } @ApiOkResponseData(CollectionsDto) @@ -125,4 +132,82 @@ export class ProjectController { ) { return this.service.getProjectCollections(projectID); } + + @ApiOkResponseData(WorkspaceUser, 'array') + @Get(':projectID/member/list') + @ApiOperation({ summary: '获取项目成员列表' }) + async getMemberList( + @Param('projectID', ParseIntPipe) projectID, + @Param('workspaceID', ParseIntPipe) workspaceID, + ): Promise { + return this.service.getMemberList(projectID, workspaceID); + } + + @ApiOkResponseData(WorkspaceUser, 'array') + @Get(':projectID/member/list/:username') + @ApiOperation({ summary: '搜索项目成员' }) + @ApiResponse({ + type: [UserEntity], + }) + async searchMemberByName( + @Param('projectID', ParseIntPipe) id, + @Param('workspaceID', ParseIntPipe) workspaceID, + @Param('username') username, + ): Promise { + return this.service.getMemberList(id, workspaceID, username); + } + + @Permissions(PermissionEnum.ADD_PROJECT_MEMBER) + @ApiCreatedResponseData(Project) + @Post(':projectID/member/add') + @ApiOperation({ summary: '添加项目成员' }) + async memberAdd( + @User() user: IUser, + @Param('projectID') projectID, + @Body() addMemberDto: WorkspaceMemberAddDto, + ) { + return this.service.memberAdd(projectID, addMemberDto); + } + + @Permissions(PermissionEnum.DELETE_PROJECT__MEMBER) + @ApiOkResponseData(Project) + @Delete(':projectID/member/remove') + @ApiOperation({ summary: '移除项目成员' }) + async memberRemove( + @User() user: IUser, + @Param('projectID') id, + @Body() createCatDto: WorkspaceMemberRemoveDto, + ) { + return this.service.memberRemove(id, createCatDto); + } + + @ApiOkResponseData(Project) + @Post(':projectID/member/leave') + @ApiOperation({ summary: '项目成员主动退出' }) + async memberLeave(@User() user: IUser, @Param('projectID') id) { + return this.service.memberLeave(user.userId, id); + } + + @Post(':projectID/member/setRole') + @ApiOperation({ summary: '设置项目成员角色' }) + async setMemberRole(@Param('projectID') projectID, @Body() dto: SetRoleDto) { + return this.service.setProjectRole(projectID, dto); + } + + @Get(':projectID/roles') + @ApiOperation({ summary: '获取当前项目角色列表' }) + async getRoles(): Promise { + return []; + } + + @ApiOkResponseData(RolePermissionDto) + @Get(':projectID/rolePermission') + @ApiOperation({ summary: '获取当前用户在项目的角色和权限' }) + async getPermission( + @User() user: IUser, + @Param('projectID') projectID, + @Param('workspaceID', ParseIntPipe) workspaceID, + ): Promise { + return this.service.getRolePermission(user.userId, projectID, workspaceID); + } } diff --git a/src/modules/workspace/project/project.service.ts b/src/modules/workspace/project/project.service.ts index 4a24148..cc2e1cd 100644 --- a/src/modules/workspace/project/project.service.ts +++ b/src/modules/workspace/project/project.service.ts @@ -1,28 +1,214 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException, OnModuleInit } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { DeepPartial, Repository } from 'typeorm'; +import { ModuleRef } from '@nestjs/core'; +import { DeepPartial, In, Like, Repository } from 'typeorm'; import { ApiGroupService } from '../apiGroup/apiGroup.service'; import { ApiDataService } from '../apiData/apiData.service'; import { EnvironmentService } from '../environment/environment.service'; import { CreateDto } from './dto/create.dto'; -import { UpdateDto } from './dto/update.dto'; +import { SetRoleDto, UpdateDto } from './dto/update.dto'; import { QueryDto } from './dto/query.dto'; import { Child, Environment, ImportDto, ImportResult } from './dto/import.dto'; import { parseAndCheckApiData, parseAndCheckEnv } from './validate'; import { Project } from '@/entities/project.entity'; +import { WorkspaceEntity } from '@/entities/workspace.entity'; +import packageJSON from 'package.json'; +import { + WorkspaceMemberAddDto, + WorkspaceMemberRemoveDto, + WorkspaceUser, +} from '@/modules/workspace/workspace.dto'; +import { ProjectUserRoleEntity } from '@/entities/project-user-role.entity'; +import { RoleEnum } from '@/enums/role.enum'; +import { RoleEntity } from '@/entities/role.entity'; +import { PermissionEntity } from '@/entities/permission.entity'; +import { RolePermissionEntity } from '@/entities/role-permission.entity'; +import { WorkspaceUserRoleEntity } from '@/entities/workspace-user-role.entity'; +import { UserService } from '@/modules/user/user.service'; @Injectable() -export class ProjectService { +export class ProjectService implements OnModuleInit { + private userService: UserService; constructor( + private moduleRef: ModuleRef, @InjectRepository(Project) private readonly repository: Repository, + @InjectRepository(WorkspaceEntity) + private workspaceRepository: Repository, + @InjectRepository(ProjectUserRoleEntity) + private readonly projectUserRoleRepo: Repository, + @InjectRepository(RoleEntity) + private roleRepo: Repository, + @InjectRepository(PermissionEntity) + private permissionRepo: Repository, + @InjectRepository(RolePermissionEntity) + private rolePermissionRepo: Repository, + @InjectRepository(WorkspaceUserRoleEntity) + private workspaceUserRoleRepo: Repository, private readonly apiDataService: ApiDataService, private readonly apiGroupService: ApiGroupService, private readonly environmentService: EnvironmentService, ) {} - async create(createDto: CreateDto) { - return await this.repository.save(createDto); + onModuleInit() { + this.userService = this.moduleRef.get(UserService, { strict: false }); + } + + async memberAdd(projectID: number, addMemberDto: WorkspaceMemberAddDto) { + return addMemberDto.userIDs.map((userID) => { + return this.projectUserRoleRepo.save({ + projectID, + userID, + roleID: RoleEnum.ProjectEditorRoleID, + }); + }); + } + + async memberRemove( + projectID: number, + createCatDto: WorkspaceMemberRemoveDto, + ) { + return this.projectUserRoleRepo.delete({ + projectID, + userID: In(createCatDto.userIDs), + }); + } + + async memberLeave(userID: number, projectID: number) { + return this.projectUserRoleRepo.delete({ + projectID, + userID, + }); + } + + async getMemberList( + projectID: number, + workspaceID: number, + username = '', + ): Promise { + const pUserRoles = await this.projectUserRoleRepo.find({ + where: { projectID }, + }); + + const wUserRoles = await this.workspaceUserRoleRepo.find({ + where: { workspaceID, roleID: RoleEnum.WorkspaceOwnerRoleID }, + }); + + const userRoles = [...pUserRoles, ...wUserRoles]; + + const users = await this.userService.find({ + where: { + id: In(userRoles.map((n) => n.userID)), + username: Like(`%${username}%`), + }, + }); + + for (const item of users) { + const target = userRoles.find((n) => n.userID === item.id); + const role = await this.roleRepo.findOneBy({ id: target.roleID }); + Reflect.set(item, 'role', role); + } + + return (users as WorkspaceUser[]).sort((a, b) => a.role.id - b.role.id); + } + + async setProjectRole(projectID: number, dto: SetRoleDto) { + const projectUserRole = await this.projectUserRoleRepo.findOneBy({ + projectID, + userID: dto.memberID, + }); + projectUserRole.roleID = dto.roleID; + + return this.projectUserRoleRepo.save(projectUserRole); + } + + async getRolePermission( + userID: number, + projectID: number, + workspaceID: number, + ) { + const workspaceOwner = await this.workspaceUserRoleRepo.findOneBy({ + userID, + workspaceID, + }); + + // 如果是空间 owner,那么直接将项目 owner 的权限返回 + if (workspaceOwner) { + const rolePerm = await this.rolePermissionRepo.findBy({ + roleID: In([ + RoleEnum.WorkspaceOwnerRoleID, + RoleEnum.ProjectOwnerRoleID, + ]), + }); + const permissions = await this.permissionRepo.findBy({ + id: In(rolePerm.map((n) => n.permissionID)), + }); + + return { + permissions: permissions.map((n) => n.name), + role: await this.roleRepo.findOneBy({ + id: RoleEnum.WorkspaceOwnerRoleID, + }), + }; + } + + const userRole = await this.projectUserRoleRepo.findOneBy({ + userID, + projectID, + }); + if (!userRole) { + throw new NotFoundException('获取失败!你不在该项目里'); + } + const role = await this.roleRepo.findOneBy({ id: userRole.roleID }); + const rolePerm = await this.rolePermissionRepo.findBy({ + roleID: userRole.roleID, + }); + const permissions = await this.permissionRepo.findBy({ + id: In(rolePerm.map((n) => n.permissionID)), + }); + + return { + permissions: permissions.map((n) => n.name), + role, + }; + } + + async hasProjectAuth(workspaceID: number, projectID: number, userID: number) { + const isWorkspaceOwner = await this.workspaceUserRoleRepo.findOneBy({ + userID, + workspaceID, + }); + return ( + isWorkspaceOwner || + this.projectUserRoleRepo.findOneBy({ + projectID, + userID, + }) + ); + } + + async create(createDto: CreateDto, workspaceID: number, userID: number) { + const workspace = await this.workspaceRepository.findOne({ + where: { + id: workspaceID, + }, + relations: { + users: true, + }, + }); + const project = await this.repository.save({ + ...createDto, + workspace, + users: workspace.users, + }); + + this.projectUserRoleRepo.save({ + userID, + projectID: project.uuid, + roleID: RoleEnum.ProjectOwnerRoleID, + }); + + return project; } async save(project: DeepPartial) { @@ -53,7 +239,7 @@ export class ProjectService { }); } - async findOne(workspaceID: number, uuid: number): Promise { + async findOne(uuid: number, workspaceID: number): Promise { return await this.repository.findOne({ where: { uuid: Number(uuid), workspace: { id: workspaceID } }, }); @@ -75,22 +261,22 @@ export class ProjectService { if (!group) { return `导入失败,id为${importDto.groupID}的分组不存在`; } - const project = await this.findOne(workspaceID, uuid); + const project = await this.findOne(uuid, workspaceID); if (project) { - const { collections, enviroments } = importDto; + const { collections, environments } = importDto; const data = { - errors: { + error: { apiData: [], group: [], - enviroments: [], + environments: [], }, successes: { apiData: [], group: [], - enviroments: [], + environments: [], }, }; - this.importEnv(enviroments, uuid, data); + this.importEnv(environments, uuid, data); return this.importCollects(collections, uuid, importDto.groupID, data); } return '导入失败,项目不存在'; @@ -113,41 +299,43 @@ export class ProjectService { .concat(apiDataFilters); } - async exportCollections(workspaceID: number, uuid: number) { - const project = await this.findOne(workspaceID, uuid); + async exportCollections(uuid: number) { + const project = await this.findOneBy(uuid); if (project) { const apiData = await this.apiDataService.findAll({ projectID: uuid }); const apiGroup = await this.apiGroupService.findAll({ projectID: uuid }); - const enviroments = await this.environmentService.findAll({ + const environments = await this.environmentService.findAll({ where: { projectID: uuid, }, }); return { + version: packageJSON.version, + project: project, collections: this.exportCollects(apiGroup, apiData), - enviroments, + environments, }; } return '导出失败,项目不存在'; } async importEnv( - enviroments: Environment[] = [], + environments: Environment[] = [], projectID: number, importResult: ImportResult, ) { - const promiseTask = enviroments.map(async (item) => { + const promiseTask = environments.map(async (item) => { const env = { ...item, parameters: item.parameters as unknown as string, }; const result = parseAndCheckEnv(env); if (!result.validate) { - importResult.errors.enviroments.push(result); + importResult.error.environments.push(result); } else { result.data.projectID = projectID; const env = await this.environmentService.create(result.data); - importResult.successes.enviroments.push({ + importResult.successes.environments.push({ name: env.name, uuid: env.uuid, }); @@ -233,15 +421,4 @@ export class ProjectService { apis, }; } - - async projectExport(projectID: number) { - return { - environment: await this.environmentService.findAll({ - where: { projectID }, - }), - group: await this.apiGroupService.findAll({ projectID }), - project: await this.repository.findOne({ where: { uuid: projectID } }), - apiData: await this.apiDataService.findAll({ projectID }), - }; - } } diff --git a/src/modules/workspace/shared/shared.controller.ts b/src/modules/workspace/shared/shared.controller.ts index c2a255b..6d5e156 100644 --- a/src/modules/workspace/shared/shared.controller.ts +++ b/src/modules/workspace/shared/shared.controller.ts @@ -1,12 +1,21 @@ -import { Controller, Get, Post, Param, Delete } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Param, + Delete, + UseGuards, +} from '@nestjs/common'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { SharedService } from './shared.service'; import { WORKSPACE_PROJECT_PREFIX } from '@/common/contants/prefix.contants'; import { ApiCreatedResponseData } from '@/common/class/res.class'; import { SharedEntity } from '@/entities/shared.entity'; +import { RolesGuard } from '@/guards'; @ApiTags('Shared') @Controller(`${WORKSPACE_PROJECT_PREFIX}/shared`) +@UseGuards(RolesGuard) export class SharedController { constructor(private readonly sharedService: SharedService) {} diff --git a/src/modules/workspace/workspace.controller.ts b/src/modules/workspace/workspace.controller.ts index 41caf89..fb83651 100644 --- a/src/modules/workspace/workspace.controller.ts +++ b/src/modules/workspace/workspace.controller.ts @@ -10,6 +10,7 @@ import { Post, Put, UnauthorizedException, + UseGuards, } from '@nestjs/common'; import { ApiBearerAuth, @@ -21,6 +22,8 @@ import { DeleteResult } from 'typeorm'; import { WorkspaceService } from './workspace.service'; import { CreateWorkspaceDto, + RolePermissionDto, + SetRoleDto, UpdateWorkspaceDto, WorkspaceMemberAddDto, WorkspaceMemberRemoveDto, @@ -29,21 +32,22 @@ import { import { WorkspaceEntity } from '@/entities/workspace.entity'; import { IUser, User } from '@/common/decorators/user.decorator'; import { UserEntity } from '@/entities/user.entity'; -import { - Collections, - ImportDto, -} from '@/modules/workspace/project/dto/import.dto'; +import { ImportDto } from '@/modules/workspace/project/dto/import.dto'; import { ProjectService } from '@/modules/workspace/project/project.service'; -import { sampleApiData } from '@/modules/workspace/apiData/samples/sample.api.data'; import { ApiDataService } from '@/modules/workspace/apiData/apiData.service'; import { ApiCreatedResponseData, ApiOkResponseData, } from '@/common/class/res.class'; +import { RolesGuard } from '@/guards'; +import { RoleEntity } from '@/entities/role.entity'; +import { Permissions } from '@/common/decorators/permission.decorator'; +import { PermissionEnum } from '@/enums/permission.enum'; @ApiBearerAuth() @ApiTags('workspace') @Controller('workspace') +@UseGuards(RolesGuard) export class WorkspaceController { constructor( private readonly workspaceService: WorkspaceService, @@ -63,14 +67,6 @@ export class WorkspaceController { user.userId, createDto, ); - const project = workspace.projects.at(0); - this.apiDataService.batchCreate( - sampleApiData.map((item) => { - Reflect.deleteProperty(item, 'uuid'); - Reflect.deleteProperty(item, 'uniqueID'); - return { ...item, projectID: project.uuid, project }; - }), - ); return workspace; } @@ -96,6 +92,7 @@ export class WorkspaceController { } } + @Permissions(PermissionEnum.UPDATE_WORKSPACE) @ApiOkResponseData(WorkspaceEntity) @Put(':workspaceID') @ApiOperation({ summary: '修改空间名称' }) @@ -106,6 +103,7 @@ export class WorkspaceController { return this.workspaceService.update(id, updateDto); } + @Permissions(PermissionEnum.DELETE_WORKSPACE) @ApiOkResponseData() @Delete(':workspaceID') @ApiOperation({ summary: '删除空间' }) @@ -163,6 +161,7 @@ export class WorkspaceController { return this.workspaceService.getMemberList(id, username); } + @Permissions(PermissionEnum.ADD_WORKSPACE_MEMBER) @ApiCreatedResponseData(WorkspaceEntity) @Post(':workspaceID/member/add') @ApiOperation({ summary: '添加空间成员' }) @@ -178,6 +177,7 @@ export class WorkspaceController { return this.workspaceService.addMembers(id, createCatDto.userIDs); } + @Permissions(PermissionEnum.DELETE_WORKSPACE__MEMBER) @ApiOkResponseData(WorkspaceEntity) @Delete(':workspaceID/member/remove') @ApiOperation({ summary: '移除空间成员' }) @@ -198,4 +198,40 @@ export class WorkspaceController { } return this.workspaceService.removeMembers(id, createCatDto.userIDs); } + + @ApiOkResponseData(WorkspaceEntity) + @Post(':workspaceID/member/leave') + @ApiOperation({ summary: '空间成员主动退出' }) + async memberLeave( + @User() user: IUser, + @Param('workspaceID') id, + ): Promise { + const workspace = await this.workspaceService.findOne({ where: { id } }); + if (!workspace) { + throw new UnauthorizedException('空间不存在'); + } + return this.workspaceService.removeMembers(id, [user.userId]); + } + + @Post(':workspaceID/member/setRole') + @ApiOperation({ summary: '设置空间成员角色' }) + async setMemberRole(@Param('workspaceID') id, @Body() dto: SetRoleDto) { + return this.workspaceService.setMemberRole(id, dto); + } + + @Get(':workspaceID/roles') + @ApiOperation({ summary: '获取当前空间角色列表' }) + async getRoles(): Promise { + return []; + } + + @ApiOkResponseData(RolePermissionDto) + @Get(':workspaceID/rolePermission') + @ApiOperation({ summary: '获取当前用户在空间的角色和权限' }) + async getRolePermission( + @User() user: IUser, + @Param('workspaceID') workspaceID, + ): Promise { + return this.workspaceService.getRolePermission(user.userId, workspaceID); + } } diff --git a/src/modules/workspace/workspace.dto.ts b/src/modules/workspace/workspace.dto.ts index 497e09f..3463a65 100644 --- a/src/modules/workspace/workspace.dto.ts +++ b/src/modules/workspace/workspace.dto.ts @@ -1,6 +1,16 @@ import { ApiProperty } from '@nestjs/swagger'; -import { ArrayNotEmpty, IsArray, IsString, MinLength } from 'class-validator'; +import { + ArrayNotEmpty, + IsArray, + IsInt, + IsString, + Max, + Min, + MinLength, +} from 'class-validator'; +import { RoleEnum } from '@/enums/role.enum'; import { UserEntity } from '@/entities/user.entity'; +import { RoleEntity } from '@/entities/role.entity'; export class CreateWorkspaceDto { @ApiProperty({ description: '空间名称' }) @@ -30,5 +40,18 @@ export class WorkspaceUser extends UserEntity { @ApiProperty({ description: '成员身份' }) @MinLength(1) @IsString() - readonly roleName: string = 'member'; + readonly role: RoleEntity; +} +export class SetRoleDto { + @IsInt() + @Min(RoleEnum.WorkspaceOwnerRoleID) + @Max(RoleEnum.WorkspaceEditorRoleID) + roleID: number; + + memberID: number; +} + +export class RolePermissionDto { + permissions: string[]; + role: RoleEntity; } diff --git a/src/modules/workspace/workspace.module.ts b/src/modules/workspace/workspace.module.ts index f5b2275..11a0fb4 100644 --- a/src/modules/workspace/workspace.module.ts +++ b/src/modules/workspace/workspace.module.ts @@ -1,5 +1,8 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { JwtService } from '@nestjs/jwt'; +import { ConfigService } from '@nestjs/config'; +import { WorkspaceUserRoleEntity } from './../../entities/workspace-user-role.entity'; import { WorkspaceController } from './workspace.controller'; import { WorkspaceService } from './workspace.service'; import { WorkspaceEntity } from '@/entities/workspace.entity'; @@ -26,6 +29,13 @@ import { UserModule } from '@/modules/user/user.module'; import { SharedController } from '@/modules/workspace/shared/shared.controller'; import { SharedService } from '@/modules/workspace/shared/shared.service'; import { SharedEntity } from '@/entities/shared.entity'; +import { AuthEntity } from '@/entities/auth.entity'; +import { AuthService } from '@/modules/auth/auth.service'; +import { JwtStrategy } from '@/guards/jwt.strategy'; +import { RolePermissionEntity } from '@/entities/role-permission.entity'; +import { PermissionEntity } from '@/entities/permission.entity'; +import { RoleEntity } from '@/entities/role.entity'; +import { ProjectUserRoleEntity } from '@/entities/project-user-role.entity'; const commonProviders = [ WorkspaceService, @@ -36,11 +46,19 @@ const commonProviders = [ MockService, EnvironmentService, SharedService, + JwtService, + AuthService, + ConfigService, ]; @Module({ imports: [ TypeOrmModule.forFeature([ WorkspaceEntity, + WorkspaceUserRoleEntity, + ProjectUserRoleEntity, + RoleEntity, + RolePermissionEntity, + PermissionEntity, UserEntity, Project, ApiData, @@ -49,6 +67,7 @@ const commonProviders = [ ApiGroup, Environment, SharedEntity, + AuthEntity, ]), UserModule, ], @@ -62,7 +81,7 @@ const commonProviders = [ ProjectController, SharedController, ], - providers: [...commonProviders], + providers: [...commonProviders, JwtStrategy], exports: [...commonProviders], }) export class WorkspaceModule {} diff --git a/src/modules/workspace/workspace.service.ts b/src/modules/workspace/workspace.service.ts index f78d2e9..246c53b 100644 --- a/src/modules/workspace/workspace.service.ts +++ b/src/modules/workspace/workspace.service.ts @@ -1,17 +1,29 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; +import { + BadRequestException, + Injectable, + OnModuleInit, + NotFoundException, +} from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { ModuleRef } from '@nestjs/core'; +import { uniqBy } from 'lodash'; import { DeleteResult, FindOneOptions, In, Like, Repository } from 'typeorm'; import { CreateWorkspaceDto, + SetRoleDto, UpdateWorkspaceDto, WorkspaceUser, } from './workspace.dto'; +import { PermissionEntity } from '@/entities/permission.entity'; import { WorkspaceEntity } from '@/entities/workspace.entity'; import { UserService } from '@/modules/user/user.service'; -import { ProjectService } from '@/modules/workspace/project/project.service'; import { Project } from '@/entities/project.entity'; import { CreateDto as ProjectCreateDto } from '@/modules/workspace/project/dto/create.dto'; +import { WorkspaceUserRoleEntity } from '@/entities/workspace-user-role.entity'; +import { RoleEnum } from '@/enums/role.enum'; +import { RoleEntity } from '@/entities/role.entity'; +import { RolePermissionEntity } from '@/entities/role-permission.entity'; +import { ProjectUserRoleEntity } from '@/entities/project-user-role.entity'; @Injectable() export class WorkspaceService implements OnModuleInit { @@ -20,7 +32,16 @@ export class WorkspaceService implements OnModuleInit { private moduleRef: ModuleRef, @InjectRepository(WorkspaceEntity) private workspaceRepository: Repository, - private projectService: ProjectService, + @InjectRepository(WorkspaceUserRoleEntity) + private workspaceUserRoleRepo: Repository, + @InjectRepository(ProjectUserRoleEntity) + private projectUserRoleRepo: Repository, + @InjectRepository(RoleEntity) + private roleRepo: Repository, + @InjectRepository(PermissionEntity) + private permissionRepo: Repository, + @InjectRepository(RolePermissionEntity) + private rolePermissionRepo: Repository, ) {} onModuleInit() { @@ -44,19 +65,24 @@ export class WorkspaceService implements OnModuleInit { projects: true, }, }); - project ??= await this.projectService.save({ - name: '默认项目', - description: createWorkspaceDto.title + '默认项目', - }); - creator.projects = (creator?.projects || []).concat(project); + // this.projectUserRoleRepo.save({ + // projectID: project.uuid, + // userID: creator.id, + // roleID: RoleEnum.ProjectOwnerRoleID, + // }) - return this.workspaceRepository.save({ + const workspace = await this.workspaceRepository.save({ ...createWorkspaceDto, creatorID, users: [await this.userService.updateUser(creator)], - projects: [project], }); + this.workspaceUserRoleRepo.save({ + workspaceID: workspace.id, + userID: creator.id, + roleID: RoleEnum.WorkspaceOwnerRoleID, + }); + return workspace; } async update( @@ -93,13 +119,17 @@ export class WorkspaceService implements OnModuleInit { username: Like(`%${username}%`), }, }); - const workspace = await this.workspaceRepository.findOneBy({ - id: workspaceId, - }); - return result.map((item) => ({ - ...item, - roleName: item.id === workspace.creatorID ? 'Owner' : 'Member', - })); + + for (const item of result) { + const userRole = await this.workspaceUserRoleRepo.findOneBy({ + userID: item.id, + workspaceID: workspaceId, + }); + const role = await this.roleRepo.findOneBy({ id: userRole.roleID }); + Reflect.set(item, 'role', role); + } + + return (result as WorkspaceUser[]).sort((a, b) => a.role.id - b.role.id); } async addMembers(workspaceId: number, userIDs: number[]) { @@ -118,11 +148,16 @@ export class WorkspaceService implements OnModuleInit { }, }); users.forEach((user) => { - user.workspaces.push({ - ...workspace, - users: [], + // user.workspaces.push({ + // ...workspace, + // users: [], + // }); + this.workspaceUserRoleRepo.save({ + userID: user.id, + roleID: RoleEnum.WorkspaceEditorRoleID, + workspaceID: workspaceId, }); - user.projects.push(...workspace.projects); + user.projects = uniqBy([...user.projects, ...workspace.projects], 'uuid'); this.userService.updateUser(user); }); workspace.users.push(...users); @@ -130,6 +165,18 @@ export class WorkspaceService implements OnModuleInit { } async removeMembers(workspaceId: number, userIDs: number[]) { + const workspaceUserRoles = await this.workspaceUserRoleRepo.findBy({ + userID: In(userIDs), + }); + const workspaceUserRoleOwners = await this.workspaceUserRoleRepo.findBy({ + roleID: RoleEnum.WorkspaceOwnerRoleID, + }); + if ( + workspaceUserRoleOwners.length === 1 && + workspaceUserRoles.some((n) => n.roleID === RoleEnum.WorkspaceOwnerRoleID) + ) { + throw new BadRequestException('操作失败!至少需要一名空间 owner'); + } const workspace = await this.workspaceRepository.findOne({ where: { id: workspaceId }, relations: { @@ -139,6 +186,45 @@ export class WorkspaceService implements OnModuleInit { workspace.users = workspace.users.filter( (user) => !userIDs.includes(user.id), ); + this.workspaceUserRoleRepo.delete({ userID: In(userIDs) }); return this.workspaceRepository.save(workspace); } + + async setMemberRole(workspaceID: number, dto: SetRoleDto) { + const userRole = await this.workspaceUserRoleRepo.findOneBy({ + workspaceID, + userID: dto.memberID, + }); + userRole.roleID = dto.roleID; + return this.workspaceUserRoleRepo.save(userRole); + } + + async getRolePermission(userID: number, workspaceID: number) { + const userRole = await this.workspaceUserRoleRepo.findOneBy({ + userID, + workspaceID, + }); + if (!userRole) { + throw new NotFoundException('获取失败!你不在该空间里'); + } + const role = await this.roleRepo.findOneBy({ id: userRole.roleID }); + const rolePerm = await this.rolePermissionRepo.findBy({ + roleID: userRole.roleID, + }); + const permissions = await this.permissionRepo.findBy({ + id: In(rolePerm.map((n) => n.permissionID)), + }); + + return { + permissions: permissions.map((n) => n.name), + role, + }; + } + + async hasWorkspaceAuth(workspaceID: number, userID: number) { + return this.workspaceUserRoleRepo.findOneBy({ + workspaceID, + userID, + }); + } } diff --git a/src/setup-swagger.ts b/src/setup-swagger.ts index 2bb4699..7c1b6cf 100644 --- a/src/setup-swagger.ts +++ b/src/setup-swagger.ts @@ -6,14 +6,17 @@ export function setupSwagger(app: INestApplication): void { const configService: ConfigService = app.get(ConfigService); // 默认为启用 - const enable = configService.get('swagger.enable', true); + // const enable = configService.get('swagger.enable', true); // 判断是否需要启用 - if (!enable) { - return; - } + // if (!enable) { + // return; + // } - const swaggerPath = configService.get('swagger.path', '/swagger-api'); + const swaggerPath = configService.get( + 'swagger.path', + '/swagger-docs', + ); const swaggerConfig = new DocumentBuilder() .setTitle(configService.get('swagger.title')) .setDescription(configService.get('swagger.desc')) diff --git a/src/shared/services/base.service.ts b/src/shared/services/base.service.ts new file mode 100644 index 0000000..6d1e6b5 --- /dev/null +++ b/src/shared/services/base.service.ts @@ -0,0 +1,67 @@ +// BaseService.ts +import { Injectable } from '@nestjs/common'; +import { + Repository, + DeleteResult, + SaveOptions, + RemoveOptions, + DeepPartial, + FindOneOptions, +} from 'typeorm'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; + +/** + * 服务基类,实现一些共有的基本方法,这样就不用每个服务类在写一遍了,直接继承该类即可 + */ +@Injectable() +export class BaseService { + protected readonly repository: Repository; + constructor(repository: Repository) { + this.repository = repository; + } + + async saveOne(entity: DeepPartial, options?: SaveOptions): Promise { + return await this.repository.save(entity, options); + } + + async saveMany(entities: DeepPartial[], options?: SaveOptions) { + return await this.repository.save(entities, options); + } + + async findOne(options?: FindOneOptions): Promise { + return await this.repository.findOne(options); + } + + async findMany(options?: FindOneOptions): Promise { + return await this.repository.find(options); + } + + async findAll(): Promise { + return await this.repository.find(); + } + + async removeOne(entity: T, options?: RemoveOptions): Promise { + return await this.repository.remove(entity, options); + } + + async removeMany(entities: T[], options?: RemoveOptions): Promise { + return await this.repository.remove(entities, options); + } + + async delete( + options?: Parameters['delete']>[number], + ): Promise { + return await this.repository.delete(options); + } + + async update( + conditions: Parameters['update']>[0], + newValue: QueryDeepPartialEntity, + ): Promise { + let updateResult = 1; + await this.repository + .update(conditions, newValue) + .catch((e) => (updateResult = 0)); + return updateResult; + } +} diff --git a/yarn.lock b/yarn.lock index 5efa681..3327a90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -365,25 +365,25 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@eslint/eslintrc@^1.3.3": - version "1.3.3" - resolved "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-1.3.3.tgz#2b044ab39fdfa75b4688184f9e573ce3c5b0ff95" - integrity sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg== +"@eslint/eslintrc@^1.4.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz#8ec64e0df3e7a1971ee1ff5158da87389f167a63" + integrity sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A== dependencies: ajv "^6.12.4" debug "^4.3.2" espree "^9.4.0" - globals "^13.15.0" + globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.11.6": - version "0.11.7" - resolved "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f" - integrity sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw== +"@humanwhocodes/config-array@^0.11.8": + version "0.11.8" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" + integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" @@ -415,28 +415,28 @@ resolved "https://registry.npmmirror.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.2.1": - version "29.2.1" - resolved "https://registry.npmmirror.com/@jest/console/-/console-29.2.1.tgz#5f2c62dcdd5ce66e94b6d6729e021758bceea090" - integrity sha512-MF8Adcw+WPLZGBiNxn76DOuczG3BhODTcMlDCA4+cFi41OkaY/lyI0XUUhi73F88Y+7IHoGmD80pN5CtxQUdSw== +"@jest/console@^29.3.1": + version "29.3.1" + resolved "https://registry.npmjs.org/@jest/console/-/console-29.3.1.tgz#3e3f876e4e47616ea3b1464b9fbda981872e9583" + integrity sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.2.1" - jest-util "^29.2.1" + jest-message-util "^29.3.1" + jest-util "^29.3.1" slash "^3.0.0" -"@jest/core@^29.2.2": - version "29.2.2" - resolved "https://registry.npmmirror.com/@jest/core/-/core-29.2.2.tgz#207aa8973d9de8769f9518732bc5f781efc3ffa7" - integrity sha512-susVl8o2KYLcZhhkvSB+b7xX575CX3TmSvxfeDjpRko7KmT89rHkXj6XkDkNpSeFMBzIENw5qIchO9HC9Sem+A== - dependencies: - "@jest/console" "^29.2.1" - "@jest/reporters" "^29.2.2" - "@jest/test-result" "^29.2.1" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" +"@jest/core@^29.3.1": + version "29.3.1" + resolved "https://registry.npmjs.org/@jest/core/-/core-29.3.1.tgz#bff00f413ff0128f4debec1099ba7dcd649774a1" + integrity sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw== + dependencies: + "@jest/console" "^29.3.1" + "@jest/reporters" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" @@ -444,32 +444,32 @@ exit "^0.1.2" graceful-fs "^4.2.9" jest-changed-files "^29.2.0" - jest-config "^29.2.2" - jest-haste-map "^29.2.1" - jest-message-util "^29.2.1" + jest-config "^29.3.1" + jest-haste-map "^29.3.1" + jest-message-util "^29.3.1" jest-regex-util "^29.2.0" - jest-resolve "^29.2.2" - jest-resolve-dependencies "^29.2.2" - jest-runner "^29.2.2" - jest-runtime "^29.2.2" - jest-snapshot "^29.2.2" - jest-util "^29.2.1" - jest-validate "^29.2.2" - jest-watcher "^29.2.2" + jest-resolve "^29.3.1" + jest-resolve-dependencies "^29.3.1" + jest-runner "^29.3.1" + jest-runtime "^29.3.1" + jest-snapshot "^29.3.1" + jest-util "^29.3.1" + jest-validate "^29.3.1" + jest-watcher "^29.3.1" micromatch "^4.0.4" - pretty-format "^29.2.1" + pretty-format "^29.3.1" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.2.2": - version "29.2.2" - resolved "https://registry.npmmirror.com/@jest/environment/-/environment-29.2.2.tgz#481e729048d42e87d04842c38aa4d09c507f53b0" - integrity sha512-OWn+Vhu0I1yxuGBJEFFekMYc8aGBGrY4rt47SOh/IFaI+D7ZHCk7pKRiSoZ2/Ml7b0Ony3ydmEHRx/tEOC7H1A== +"@jest/environment@^29.3.1": + version "29.3.1" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz#eb039f726d5fcd14698acd072ac6576d41cfcaa6" + integrity sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag== dependencies: - "@jest/fake-timers" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/fake-timers" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" - jest-mock "^29.2.2" + jest-mock "^29.3.1" "@jest/expect-utils@^29.0.3": version "29.0.3" @@ -478,53 +478,53 @@ dependencies: jest-get-type "^29.0.0" -"@jest/expect-utils@^29.2.2": - version "29.2.2" - resolved "https://registry.npmmirror.com/@jest/expect-utils/-/expect-utils-29.2.2.tgz#460a5b5a3caf84d4feb2668677393dd66ff98665" - integrity sha512-vwnVmrVhTmGgQzyvcpze08br91OL61t9O0lJMDyb6Y/D8EKQ9V7rGUb/p7PDt0GPzK0zFYqXWFo4EO2legXmkg== +"@jest/expect-utils@^29.3.1": + version "29.3.1" + resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.3.1.tgz#531f737039e9b9e27c42449798acb5bba01935b6" + integrity sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g== dependencies: jest-get-type "^29.2.0" -"@jest/expect@^29.2.2": - version "29.2.2" - resolved "https://registry.npmmirror.com/@jest/expect/-/expect-29.2.2.tgz#81edbd33afbde7795ca07ff6b4753d15205032e4" - integrity sha512-zwblIZnrIVt8z/SiEeJ7Q9wKKuB+/GS4yZe9zw7gMqfGf4C5hBLGrVyxu1SzDbVSqyMSlprKl3WL1r80cBNkgg== +"@jest/expect@^29.3.1": + version "29.3.1" + resolved "https://registry.npmjs.org/@jest/expect/-/expect-29.3.1.tgz#456385b62894349c1d196f2d183e3716d4c6a6cd" + integrity sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg== dependencies: - expect "^29.2.2" - jest-snapshot "^29.2.2" + expect "^29.3.1" + jest-snapshot "^29.3.1" -"@jest/fake-timers@^29.2.2": - version "29.2.2" - resolved "https://registry.npmmirror.com/@jest/fake-timers/-/fake-timers-29.2.2.tgz#d8332e6e3cfa99cde4bc87d04a17d6b699deb340" - integrity sha512-nqaW3y2aSyZDl7zQ7t1XogsxeavNpH6kkdq+EpXncIDvAkjvFD7hmhcIs1nWloengEWUoWqkqSA6MSbf9w6DgA== +"@jest/fake-timers@^29.3.1": + version "29.3.1" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.3.1.tgz#b140625095b60a44de820876d4c14da1aa963f67" + integrity sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" "@sinonjs/fake-timers" "^9.1.2" "@types/node" "*" - jest-message-util "^29.2.1" - jest-mock "^29.2.2" - jest-util "^29.2.1" + jest-message-util "^29.3.1" + jest-mock "^29.3.1" + jest-util "^29.3.1" -"@jest/globals@^29.2.2": - version "29.2.2" - resolved "https://registry.npmmirror.com/@jest/globals/-/globals-29.2.2.tgz#205ff1e795aa774301c2c0ba0be182558471b845" - integrity sha512-/nt+5YMh65kYcfBhj38B3Hm0Trk4IsuMXNDGKE/swp36yydBWfz3OXkLqkSvoAtPW8IJMSJDFCbTM2oj5SNprw== +"@jest/globals@^29.3.1": + version "29.3.1" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-29.3.1.tgz#92be078228e82d629df40c3656d45328f134a0c6" + integrity sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q== dependencies: - "@jest/environment" "^29.2.2" - "@jest/expect" "^29.2.2" - "@jest/types" "^29.2.1" - jest-mock "^29.2.2" + "@jest/environment" "^29.3.1" + "@jest/expect" "^29.3.1" + "@jest/types" "^29.3.1" + jest-mock "^29.3.1" -"@jest/reporters@^29.2.2": - version "29.2.2" - resolved "https://registry.npmmirror.com/@jest/reporters/-/reporters-29.2.2.tgz#69b395f79c3a97ce969ce05ccf1a482e5d6de290" - integrity sha512-AzjL2rl2zJC0njIzcooBvjA4sJjvdoq98sDuuNs4aNugtLPSQ+91nysGKRF0uY1to5k0MdGMdOBggUsPqvBcpA== +"@jest/reporters@^29.3.1": + version "29.3.1" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-29.3.1.tgz#9a6d78c109608e677c25ddb34f907b90e07b4310" + integrity sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.2.1" - "@jest/test-result" "^29.2.1" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/console" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@jridgewell/trace-mapping" "^0.3.15" "@types/node" "*" chalk "^4.0.0" @@ -537,9 +537,9 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.2.1" - jest-util "^29.2.1" - jest-worker "^29.2.1" + jest-message-util "^29.3.1" + jest-util "^29.3.1" + jest-worker "^29.3.1" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" @@ -561,42 +561,42 @@ callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.2.1": - version "29.2.1" - resolved "https://registry.npmmirror.com/@jest/test-result/-/test-result-29.2.1.tgz#f42dbf7b9ae465d0a93eee6131473b8bb3bd2edb" - integrity sha512-lS4+H+VkhbX6z64tZP7PAUwPqhwj3kbuEHcaLuaBuB+riyaX7oa1txe0tXgrFj5hRWvZKvqO7LZDlNWeJ7VTPA== +"@jest/test-result@^29.3.1": + version "29.3.1" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-29.3.1.tgz#92cd5099aa94be947560a24610aa76606de78f50" + integrity sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw== dependencies: - "@jest/console" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/console" "^29.3.1" + "@jest/types" "^29.3.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.2.2": - version "29.2.2" - resolved "https://registry.npmmirror.com/@jest/test-sequencer/-/test-sequencer-29.2.2.tgz#4ac7487b237e517a1f55e7866fb5553f6e0168b9" - integrity sha512-Cuc1znc1pl4v9REgmmLf0jBd3Y65UXJpioGYtMr/JNpQEIGEzkmHhy6W6DLbSsXeUA13TDzymPv0ZGZ9jH3eIw== +"@jest/test-sequencer@^29.3.1": + version "29.3.1" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz#fa24b3b050f7a59d48f7ef9e0b782ab65123090d" + integrity sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA== dependencies: - "@jest/test-result" "^29.2.1" + "@jest/test-result" "^29.3.1" graceful-fs "^4.2.9" - jest-haste-map "^29.2.1" + jest-haste-map "^29.3.1" slash "^3.0.0" -"@jest/transform@^29.2.2": - version "29.2.2" - resolved "https://registry.npmmirror.com/@jest/transform/-/transform-29.2.2.tgz#dfc03fc092b31ffea0c55917728e75bfcf8b5de6" - integrity sha512-aPe6rrletyuEIt2axxgdtxljmzH8O/nrov4byy6pDw9S8inIrTV+2PnjyP/oFHMSynzGxJ2s6OHowBNMXp/Jzg== +"@jest/transform@^29.3.1": + version "29.3.1" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-29.3.1.tgz#1e6bd3da4af50b5c82a539b7b1f3770568d6e36d" + integrity sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" "@jridgewell/trace-mapping" "^0.3.15" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" - convert-source-map "^1.4.0" + convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.2.1" + jest-haste-map "^29.3.1" jest-regex-util "^29.2.0" - jest-util "^29.2.1" + jest-util "^29.3.1" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" @@ -614,10 +614,10 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jest/types@^29.2.1": - version "29.2.1" - resolved "https://registry.npmmirror.com/@jest/types/-/types-29.2.1.tgz#ec9c683094d4eb754e41e2119d8bdaef01cf6da0" - integrity sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw== +"@jest/types@^29.3.1": + version "29.3.1" + resolved "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz#7c5a80777cb13e703aeec6788d044150341147e3" + integrity sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA== dependencies: "@jest/schemas" "^29.0.0" "@types/istanbul-lib-coverage" "^2.0.0" @@ -725,13 +725,13 @@ webpack "5.74.0" webpack-node-externals "3.0.0" -"@nestjs/common@^9.1.6": - version "9.1.6" - resolved "https://registry.npmmirror.com/@nestjs/common/-/common-9.1.6.tgz#79d71fb702816c07d8479f5aec71815c1762eea7" - integrity sha512-9Ttk9va/BwEab36RSXLZdRoPUX3DZHUpzseKEfqHVhnaUIsIMt7lVd79GQ1FroQ2FZqoCwcLyBowevXhrE1Wnw== +"@nestjs/common@^9.2.0": + version "9.2.1" + resolved "https://registry.npmjs.org/@nestjs/common/-/common-9.2.1.tgz#24de19ee85a8f1747288980fe517b12753cf66ea" + integrity sha512-nZuo3oDsSSlC5mti/M2aCWTEIfHPGDXmBwWgPeCpRbrNz3IWd109rkajll+yxgidVjznAdBS9y00JkAVJblNYw== dependencies: iterare "1.2.1" - tslib "2.4.0" + tslib "2.4.1" uuid "9.0.0" "@nestjs/config@^2.2.0": @@ -744,17 +744,17 @@ lodash "4.17.21" uuid "8.3.2" -"@nestjs/core@^9.1.6": - version "9.1.6" - resolved "https://registry.npmmirror.com/@nestjs/core/-/core-9.1.6.tgz#b73a453a40470a1456fc404384b195e816da0828" - integrity sha512-B52nYYTDSH72f1DU0G14NQSPCviXRE9fCp2/gUHuWIfVfBwcmVBAxVgyB/jAIUAhhj1f5/2odwUiw194xYtRRA== +"@nestjs/core@^9.2.0": + version "9.2.1" + resolved "https://registry.npmjs.org/@nestjs/core/-/core-9.2.1.tgz#598e51a421a0aaafc568c1a02499f7c1f9491caf" + integrity sha512-a9GkXuu8uXgNgCVW+17iI8kLCltO+HwHpU2IhR+32JKnN2WEQ1YEWU4t3GJ2MNq44YkjIw9zrKvFkjJBlYrNbQ== dependencies: "@nuxtjs/opencollective" "0.3.2" fast-safe-stringify "2.1.1" iterare "1.2.1" object-hash "3.0.0" path-to-regexp "3.2.0" - tslib "2.4.0" + tslib "2.4.1" uuid "9.0.0" "@nestjs/jwt@^9.0.0": @@ -780,16 +780,16 @@ resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-9.0.0.tgz#0571bb08f8043456bc6df44cd4f59ca5f10c9b9f" integrity sha512-Gnh8n1wzFPOLSS/94X1sUP4IRAoXTgG4odl7/AO5h+uwscEGXxJFercrZfqdAwkWhqkKWbsntM3j5mRy/6ZQDA== -"@nestjs/platform-express@^9.1.6": - version "9.1.6" - resolved "https://registry.npmmirror.com/@nestjs/platform-express/-/platform-express-9.1.6.tgz#01e0dc29bb02d1a3820a5da4f2a0a534b97c75cb" - integrity sha512-zZuB1g6yIPytPIzp0EYKWT+1d02hOQ5aU6VdtO6Qg0Wn1RjdjGh3kScsqdamMEjzt0WAfL8DjE5oqrLgvashsw== +"@nestjs/platform-express@^9.2.0": + version "9.2.1" + resolved "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-9.2.1.tgz#74b88a531239eaee3fe23af2f2912ebef313866f" + integrity sha512-7PecaXt8lrdS1p6Vb1X/am3GGv+EO1VahyDzaEGOK6C0zwhc0VPfLtwihkjjfhS6BjpRIXXgviwEjONUvxVZnA== dependencies: body-parser "1.20.1" cors "2.8.5" express "4.18.2" multer "1.4.4-lts.1" - tslib "2.4.0" + tslib "2.4.1" "@nestjs/schematics@^9.0.0", "@nestjs/schematics@^9.0.3": version "9.0.3" @@ -813,12 +813,12 @@ path-to-regexp "3.2.0" swagger-ui-dist "4.15.1" -"@nestjs/testing@^9.1.6": - version "9.1.6" - resolved "https://registry.npmmirror.com/@nestjs/testing/-/testing-9.1.6.tgz#e85601d8cb5158bec4a5029b435c352dc440be9c" - integrity sha512-3kdTgiv5DHRHMQ/befLWd/60LZWjOM2V+SwOLqGkITt1yjo+jpkDze8/f0ZXVGEa6pcWYZ5oeedbL2T1nyUKSQ== +"@nestjs/testing@^9.2.0": + version "9.2.1" + resolved "https://registry.npmjs.org/@nestjs/testing/-/testing-9.2.1.tgz#2a3f64214d58ec4ab878862395407947671e4ece" + integrity sha512-lemXZdRSuqoZ87l0orCrS/c7gqwxeduIFOd21g9g2RUeQ4qlWPegbQDKASzbfC28klPyrgJLW4MNq7uv2JwV8w== dependencies: - tslib "2.4.0" + tslib "2.4.1" "@nestjs/typeorm@^9.0.1": version "9.0.1" @@ -1040,10 +1040,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@29.2.1": - version "29.2.1" - resolved "https://registry.npmmirror.com/@types/jest/-/jest-29.2.1.tgz#31fda30bdf2861706abc5f1730be78bed54f83ee" - integrity sha512-nKixEdnGDqFOZkMTF74avFNr3yRqB1ZJ6sRZv5/28D5x2oLN14KApv7F9mfDT/vUic0L3tRCsh3XWpWjtJisUQ== +"@types/jest@29.2.2": + version "29.2.2" + resolved "https://registry.npmjs.org/@types/jest/-/jest-29.2.2.tgz#874e7dc6702fa6a3fe6107792aa98636dcc480b4" + integrity sha512-og1wAmdxKoS71K2ZwSVqWPX6OVn3ihZ6ZT2qvZvZQm90lJVDyXIjYcu4Khx2CNIeaFv12rOU/YObOsI3VOkzog== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -1065,6 +1065,11 @@ dependencies: "@types/node" "*" +"@types/lodash@^4.14.191": + version "4.14.191" + resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa" + integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ== + "@types/mime@*": version "3.0.1" resolved "https://registry.npmmirror.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" @@ -1167,14 +1172,14 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.42.0": - version "5.42.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.42.0.tgz#36a8c0c379870127059889a9cc7e05c260d2aaa5" - integrity sha512-5TJh2AgL6+wpL8H/GTSjNb4WrjKoR2rqvFxR/DDTqYNk6uXn8BJMEcncLSpMbf/XV1aS0jAjYwn98uvVCiAywQ== +"@typescript-eslint/eslint-plugin@^5.42.1": + version "5.46.1" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.1.tgz#098abb4c9354e19f460d57ab18bff1f676a6cff0" + integrity sha512-YpzNv3aayRBwjs4J3oz65eVLXc9xx0PDbIRisHj+dYhvBn02MjYOD96P8YGiWEIFBrojaUjxvkaUpakD82phsA== dependencies: - "@typescript-eslint/scope-manager" "5.42.0" - "@typescript-eslint/type-utils" "5.42.0" - "@typescript-eslint/utils" "5.42.0" + "@typescript-eslint/scope-manager" "5.46.1" + "@typescript-eslint/type-utils" "5.46.1" + "@typescript-eslint/utils" "5.46.1" debug "^4.3.4" ignore "^5.2.0" natural-compare-lite "^1.4.0" @@ -1182,72 +1187,72 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.42.0": - version "5.42.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-5.42.0.tgz#be0ffbe279e1320e3d15e2ef0ad19262f59e9240" - integrity sha512-Ixh9qrOTDRctFg3yIwrLkgf33AHyEIn6lhyf5cCfwwiGtkWhNpVKlEZApi3inGQR/barWnY7qY8FbGKBO7p3JA== +"@typescript-eslint/parser@^5.42.1": + version "5.46.1" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.1.tgz#1fc8e7102c1141eb64276c3b89d70da8c0ba5699" + integrity sha512-RelQ5cGypPh4ySAtfIMBzBGyrNerQcmfA1oJvPj5f+H4jI59rl9xxpn4bonC0tQvUKOEN7eGBFWxFLK3Xepneg== dependencies: - "@typescript-eslint/scope-manager" "5.42.0" - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/typescript-estree" "5.42.0" + "@typescript-eslint/scope-manager" "5.46.1" + "@typescript-eslint/types" "5.46.1" + "@typescript-eslint/typescript-estree" "5.46.1" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.42.0": - version "5.42.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-5.42.0.tgz#e1f2bb26d3b2a508421ee2e3ceea5396b192f5ef" - integrity sha512-l5/3IBHLH0Bv04y+H+zlcLiEMEMjWGaCX6WyHE5Uk2YkSGAMlgdUPsT/ywTSKgu9D1dmmKMYgYZijObfA39Wow== +"@typescript-eslint/scope-manager@5.46.1": + version "5.46.1" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.1.tgz#70af8425c79bbc1178b5a63fb51102ddf48e104a" + integrity sha512-iOChVivo4jpwUdrJZyXSMrEIM/PvsbbDOX1y3UCKjSgWn+W89skxWaYXACQfxmIGhPVpRWK/VWPYc+bad6smIA== dependencies: - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/visitor-keys" "5.42.0" + "@typescript-eslint/types" "5.46.1" + "@typescript-eslint/visitor-keys" "5.46.1" -"@typescript-eslint/type-utils@5.42.0": - version "5.42.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-5.42.0.tgz#4206d7192d4fe903ddf99d09b41d4ac31b0b7dca" - integrity sha512-HW14TXC45dFVZxnVW8rnUGnvYyRC0E/vxXShFCthcC9VhVTmjqOmtqj6H5rm9Zxv+ORxKA/1aLGD7vmlLsdlOg== +"@typescript-eslint/type-utils@5.46.1": + version "5.46.1" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.46.1.tgz#195033e4b30b51b870dfcf2828e88d57b04a11cc" + integrity sha512-V/zMyfI+jDmL1ADxfDxjZ0EMbtiVqj8LUGPAGyBkXXStWmCUErMpW873zEHsyguWCuq2iN4BrlWUkmuVj84yng== dependencies: - "@typescript-eslint/typescript-estree" "5.42.0" - "@typescript-eslint/utils" "5.42.0" + "@typescript-eslint/typescript-estree" "5.46.1" + "@typescript-eslint/utils" "5.46.1" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.42.0": - version "5.42.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/types/-/types-5.42.0.tgz#5aeff9b5eced48f27d5b8139339bf1ef805bad7a" - integrity sha512-t4lzO9ZOAUcHY6bXQYRuu+3SSYdD9TS8ooApZft4WARt4/f2Cj/YpvbTe8A4GuhT4bNW72goDMOy7SW71mZwGw== +"@typescript-eslint/types@5.46.1": + version "5.46.1" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.1.tgz#4e9db2107b9a88441c4d5ecacde3bb7a5ebbd47e" + integrity sha512-Z5pvlCaZgU+93ryiYUwGwLl9AQVB/PQ1TsJ9NZ/gHzZjN7g9IAn6RSDkpCV8hqTwAiaj6fmCcKSQeBPlIpW28w== -"@typescript-eslint/typescript-estree@5.42.0": - version "5.42.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.0.tgz#2592d24bb5f89bf54a63384ff3494870f95b3fd8" - integrity sha512-2O3vSq794x3kZGtV7i4SCWZWCwjEtkWfVqX4m5fbUBomOsEOyd6OAD1qU2lbvV5S8tgy/luJnOYluNyYVeOTTg== +"@typescript-eslint/typescript-estree@5.46.1": + version "5.46.1" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.1.tgz#5358088f98a8f9939355e0996f9c8f41c25eced2" + integrity sha512-j9W4t67QiNp90kh5Nbr1w92wzt+toiIsaVPnEblB2Ih2U9fqBTyqV9T3pYWZBRt6QoMh/zVWP59EpuCjc4VRBg== dependencies: - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/visitor-keys" "5.42.0" + "@typescript-eslint/types" "5.46.1" + "@typescript-eslint/visitor-keys" "5.46.1" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.42.0": - version "5.42.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-5.42.0.tgz#f06bd43b9a9a06ed8f29600273240e84a53f2f15" - integrity sha512-JZ++3+h1vbeG1NUECXQZE3hg0kias9kOtcQr3+JVQ3whnjvKuMyktJAAIj6743OeNPnGBmjj7KEmiDL7qsdnCQ== +"@typescript-eslint/utils@5.46.1": + version "5.46.1" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.46.1.tgz#7da3c934d9fd0eb4002a6bb3429f33298b469b4a" + integrity sha512-RBdBAGv3oEpFojaCYT4Ghn4775pdjvwfDOfQ2P6qzNVgQOVrnSPe5/Pb88kv7xzYQjoio0eKHKB9GJ16ieSxvA== dependencies: "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.42.0" - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/typescript-estree" "5.42.0" + "@typescript-eslint/scope-manager" "5.46.1" + "@typescript-eslint/types" "5.46.1" + "@typescript-eslint/typescript-estree" "5.46.1" eslint-scope "^5.1.1" eslint-utils "^3.0.0" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.42.0": - version "5.42.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.0.tgz#ee8d62d486f41cfe646632fab790fbf0c1db5bb0" - integrity sha512-QHbu5Hf/2lOEOwy+IUw0GoSCuAzByTAWWrOTKzTzsotiUnWFpuKnXcAhC9YztAf2EElQ0VvIK+pHJUPkM0q7jg== +"@typescript-eslint/visitor-keys@5.46.1": + version "5.46.1" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.1.tgz#126cc6fe3c0f83608b2b125c5d9daced61394242" + integrity sha512-jczZ9noovXwy59KjRTk1OftT78pwygdcmCuBf8yMoWt/8O8l+6x2LSEze0E4TeepXK4MezW3zGSyoDRZK7Y9cg== dependencies: - "@typescript-eslint/types" "5.42.0" + "@typescript-eslint/types" "5.46.1" eslint-visitor-keys "^3.3.0" "@webassemblyjs/ast@1.11.1": @@ -1567,12 +1572,12 @@ axios@1.1.3: form-data "^4.0.0" proxy-from-env "^1.1.0" -babel-jest@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/babel-jest/-/babel-jest-29.2.2.tgz#2c15abd8c2081293c9c3f4f80a4ed1d51542fee5" - integrity sha512-kkq2QSDIuvpgfoac3WZ1OOcHsQQDU5xYk2Ql7tLdJ8BVAYbefEXal+NfS45Y5LVZA7cxC8KYcQMObpCt1J025w== +babel-jest@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz#05c83e0d128cd48c453eea851482a38782249f44" + integrity sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA== dependencies: - "@jest/transform" "^29.2.2" + "@jest/transform" "^29.3.1" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" babel-preset-jest "^29.2.0" @@ -1992,13 +1997,18 @@ content-type@~1.0.4: resolved "https://registry.npmmirror.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.8.0" resolved "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== dependencies: safe-buffer "~5.1.1" +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -2069,6 +2079,11 @@ date-fns@^2.28.0: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== +dayjs@^1.11.7: + version "1.11.7" + resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" + integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== + debug@2.6.9, debug@^2.6.9: version "2.6.9" resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -2158,10 +2173,10 @@ diff-sequences@^29.0.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.0.0.tgz#bae49972ef3933556bcb0800b72e8579d19d9e4f" integrity sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA== -diff-sequences@^29.2.0: - version "29.2.0" - resolved "https://registry.npmmirror.com/diff-sequences/-/diff-sequences-29.2.0.tgz#4c55b5b40706c7b5d2c5c75999a50c56d214e8f6" - integrity sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw== +diff-sequences@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz#104b5b95fe725932421a9c6e5b4bef84c3f2249e" + integrity sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ== diff@^4.0.1: version "4.0.2" @@ -2418,13 +2433,13 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.26.0: - version "8.26.0" - resolved "https://registry.npmmirror.com/eslint/-/eslint-8.26.0.tgz#2bcc8836e6c424c4ac26a5674a70d44d84f2181d" - integrity sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg== +eslint@^8.27.0: + version "8.30.0" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz#83a506125d089eef7c5b5910eeea824273a33f50" + integrity sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ== dependencies: - "@eslint/eslintrc" "^1.3.3" - "@humanwhocodes/config-array" "^0.11.6" + "@eslint/eslintrc" "^1.4.0" + "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" ajv "^6.10.0" @@ -2443,7 +2458,7 @@ eslint@^8.26.0: file-entry-cache "^6.0.1" find-up "^5.0.0" glob-parent "^6.0.2" - globals "^13.15.0" + globals "^13.19.0" grapheme-splitter "^1.0.4" ignore "^5.2.0" import-fresh "^3.0.0" @@ -2562,16 +2577,16 @@ expect@^29.0.0: jest-message-util "^29.0.3" jest-util "^29.0.3" -expect@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/expect/-/expect-29.2.2.tgz#ba2dd0d7e818727710324a6e7f13dd0e6d086106" - integrity sha512-hE09QerxZ5wXiOhqkXy5d2G9ar+EqOyifnCXCpMNu+vZ6DG9TJ6CO2c2kPDSLqERTTWrO7OZj8EkYHQqSd78Yw== +expect@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz#92877aad3f7deefc2e3f6430dd195b92295554a6" + integrity sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA== dependencies: - "@jest/expect-utils" "^29.2.2" + "@jest/expect-utils" "^29.3.1" jest-get-type "^29.2.0" - jest-matcher-utils "^29.2.2" - jest-message-util "^29.2.1" - jest-util "^29.2.1" + jest-matcher-utils "^29.3.1" + jest-message-util "^29.3.1" + jest-util "^29.3.1" express@4.18.2: version "4.18.2" @@ -2924,10 +2939,10 @@ globals@^11.1.0: resolved "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.15.0: - version "13.17.0" - resolved "https://registry.npmmirror.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" - integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== +globals@^13.19.0: + version "13.19.0" + resolved "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz#7a42de8e6ad4f7242fbcca27ea5b23aca367b5c8" + integrity sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ== dependencies: type-fest "^0.20.2" @@ -3358,74 +3373,74 @@ jest-changed-files@^29.2.0: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest-circus/-/jest-circus-29.2.2.tgz#1dc4d35fd49bf5e64d3cc505fb2db396237a6dfa" - integrity sha512-upSdWxx+Mh4DV7oueuZndJ1NVdgtTsqM4YgywHEx05UMH5nxxA2Qu9T9T9XVuR021XxqSoaKvSmmpAbjwwwxMw== +jest-circus@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-29.3.1.tgz#177d07c5c0beae8ef2937a67de68f1e17bbf1b4a" + integrity sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg== dependencies: - "@jest/environment" "^29.2.2" - "@jest/expect" "^29.2.2" - "@jest/test-result" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/environment" "^29.3.1" + "@jest/expect" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^29.2.1" - jest-matcher-utils "^29.2.2" - jest-message-util "^29.2.1" - jest-runtime "^29.2.2" - jest-snapshot "^29.2.2" - jest-util "^29.2.1" + jest-each "^29.3.1" + jest-matcher-utils "^29.3.1" + jest-message-util "^29.3.1" + jest-runtime "^29.3.1" + jest-snapshot "^29.3.1" + jest-util "^29.3.1" p-limit "^3.1.0" - pretty-format "^29.2.1" + pretty-format "^29.3.1" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest-cli/-/jest-cli-29.2.2.tgz#feaf0aa57d327e80d4f2f18d5f8cd2e77cac5371" - integrity sha512-R45ygnnb2CQOfd8rTPFR+/fls0d+1zXS6JPYTBBrnLPrhr58SSuPTiA5Tplv8/PXpz4zXR/AYNxmwIj6J6nrvg== +jest-cli@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-29.3.1.tgz#e89dff427db3b1df50cea9a393ebd8640790416d" + integrity sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ== dependencies: - "@jest/core" "^29.2.2" - "@jest/test-result" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/core" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/types" "^29.3.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.2.2" - jest-util "^29.2.1" - jest-validate "^29.2.2" + jest-config "^29.3.1" + jest-util "^29.3.1" + jest-validate "^29.3.1" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest-config/-/jest-config-29.2.2.tgz#bf98623a46454d644630c1f0de8bba3f495c2d59" - integrity sha512-Q0JX54a5g1lP63keRfKR8EuC7n7wwny2HoTRDb8cx78IwQOiaYUVZAdjViY3WcTxpR02rPUpvNVmZ1fkIlZPcw== +jest-config@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-29.3.1.tgz#0bc3dcb0959ff8662957f1259947aedaefb7f3c6" + integrity sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.2.2" - "@jest/types" "^29.2.1" - babel-jest "^29.2.2" + "@jest/test-sequencer" "^29.3.1" + "@jest/types" "^29.3.1" + babel-jest "^29.3.1" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.2.2" - jest-environment-node "^29.2.2" + jest-circus "^29.3.1" + jest-environment-node "^29.3.1" jest-get-type "^29.2.0" jest-regex-util "^29.2.0" - jest-resolve "^29.2.2" - jest-runner "^29.2.2" - jest-util "^29.2.1" - jest-validate "^29.2.2" + jest-resolve "^29.3.1" + jest-runner "^29.3.1" + jest-util "^29.3.1" + jest-validate "^29.3.1" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.2.1" + pretty-format "^29.3.1" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -3439,15 +3454,15 @@ jest-diff@^29.0.3: jest-get-type "^29.0.0" pretty-format "^29.0.3" -jest-diff@^29.2.1: - version "29.2.1" - resolved "https://registry.npmmirror.com/jest-diff/-/jest-diff-29.2.1.tgz#027e42f5a18b693fb2e88f81b0ccab533c08faee" - integrity sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA== +jest-diff@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz#d8215b72fed8f1e647aed2cae6c752a89e757527" + integrity sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw== dependencies: chalk "^4.0.0" - diff-sequences "^29.2.0" + diff-sequences "^29.3.1" jest-get-type "^29.2.0" - pretty-format "^29.2.1" + pretty-format "^29.3.1" jest-docblock@^29.2.0: version "29.2.0" @@ -3456,28 +3471,28 @@ jest-docblock@^29.2.0: dependencies: detect-newline "^3.0.0" -jest-each@^29.2.1: - version "29.2.1" - resolved "https://registry.npmmirror.com/jest-each/-/jest-each-29.2.1.tgz#6b0a88ee85c2ba27b571a6010c2e0c674f5c9b29" - integrity sha512-sGP86H/CpWHMyK3qGIGFCgP6mt+o5tu9qG4+tobl0LNdgny0aitLXs9/EBacLy3Bwqy+v4uXClqJgASJWcruYw== +jest-each@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-29.3.1.tgz#bc375c8734f1bb96625d83d1ca03ef508379e132" + integrity sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" chalk "^4.0.0" jest-get-type "^29.2.0" - jest-util "^29.2.1" - pretty-format "^29.2.1" + jest-util "^29.3.1" + pretty-format "^29.3.1" -jest-environment-node@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest-environment-node/-/jest-environment-node-29.2.2.tgz#a64b272773870c3a947cd338c25fd34938390bc2" - integrity sha512-B7qDxQjkIakQf+YyrqV5dICNs7tlCO55WJ4OMSXsqz1lpI/0PmeuXdx2F7eU8rnPbRkUR/fItSSUh0jvE2y/tw== +jest-environment-node@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.3.1.tgz#5023b32472b3fba91db5c799a0d5624ad4803e74" + integrity sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag== dependencies: - "@jest/environment" "^29.2.2" - "@jest/fake-timers" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/environment" "^29.3.1" + "@jest/fake-timers" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" - jest-mock "^29.2.2" - jest-util "^29.2.1" + jest-mock "^29.3.1" + jest-util "^29.3.1" jest-get-type@^29.0.0: version "29.0.0" @@ -3489,32 +3504,32 @@ jest-get-type@^29.2.0: resolved "https://registry.npmmirror.com/jest-get-type/-/jest-get-type-29.2.0.tgz#726646f927ef61d583a3b3adb1ab13f3a5036408" integrity sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA== -jest-haste-map@^29.2.1: - version "29.2.1" - resolved "https://registry.npmmirror.com/jest-haste-map/-/jest-haste-map-29.2.1.tgz#f803fec57f8075e6c55fb5cd551f99a72471c699" - integrity sha512-wF460rAFmYc6ARcCFNw4MbGYQjYkvjovb9GBT+W10Um8q5nHq98jD6fHZMDMO3tA56S8XnmNkM8GcA8diSZfnA== +jest-haste-map@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.3.1.tgz#af83b4347f1dae5ee8c2fb57368dc0bb3e5af843" + integrity sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" jest-regex-util "^29.2.0" - jest-util "^29.2.1" - jest-worker "^29.2.1" + jest-util "^29.3.1" + jest-worker "^29.3.1" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.2.1: - version "29.2.1" - resolved "https://registry.npmmirror.com/jest-leak-detector/-/jest-leak-detector-29.2.1.tgz#ec551686b7d512ec875616c2c3534298b1ffe2fc" - integrity sha512-1YvSqYoiurxKOJtySc+CGVmw/e1v4yNY27BjWTVzp0aTduQeA7pdieLiW05wTYG/twlKOp2xS/pWuikQEmklug== +jest-leak-detector@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz#95336d020170671db0ee166b75cd8ef647265518" + integrity sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA== dependencies: jest-get-type "^29.2.0" - pretty-format "^29.2.1" + pretty-format "^29.3.1" jest-matcher-utils@^29.0.3: version "29.0.3" @@ -3526,15 +3541,15 @@ jest-matcher-utils@^29.0.3: jest-get-type "^29.0.0" pretty-format "^29.0.3" -jest-matcher-utils@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz#9202f8e8d3a54733266784ce7763e9a08688269c" - integrity sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw== +jest-matcher-utils@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz#6e7f53512f80e817dfa148672bd2d5d04914a572" + integrity sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ== dependencies: chalk "^4.0.0" - jest-diff "^29.2.1" + jest-diff "^29.3.1" jest-get-type "^29.2.0" - pretty-format "^29.2.1" + pretty-format "^29.3.1" jest-message-util@^29.0.3: version "29.0.3" @@ -3551,29 +3566,29 @@ jest-message-util@^29.0.3: slash "^3.0.0" stack-utils "^2.0.3" -jest-message-util@^29.2.1: - version "29.2.1" - resolved "https://registry.npmmirror.com/jest-message-util/-/jest-message-util-29.2.1.tgz#3a51357fbbe0cc34236f17a90d772746cf8d9193" - integrity sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw== +jest-message-util@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz#37bc5c468dfe5120712053dd03faf0f053bd6adb" + integrity sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^29.2.1" + pretty-format "^29.3.1" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest-mock/-/jest-mock-29.2.2.tgz#9045618b3f9d27074bbcf2d55bdca6a5e2e8bca7" - integrity sha512-1leySQxNAnivvbcx0sCB37itu8f4OX2S/+gxLAV4Z62shT4r4dTG9tACDywUAEZoLSr36aYUTsVp3WKwWt4PMQ== +jest-mock@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz#60287d92e5010979d01f218c6b215b688e0f313e" + integrity sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" "@types/node" "*" - jest-util "^29.2.1" + jest-util "^29.3.1" jest-pnp-resolver@^1.2.2: version "1.2.2" @@ -3585,88 +3600,88 @@ jest-regex-util@^29.2.0: resolved "https://registry.npmmirror.com/jest-regex-util/-/jest-regex-util-29.2.0.tgz#82ef3b587e8c303357728d0322d48bbfd2971f7b" integrity sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA== -jest-resolve-dependencies@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.2.2.tgz#1f444766f37a25f1490b5137408b6ff746a05d64" - integrity sha512-wWOmgbkbIC2NmFsq8Lb+3EkHuW5oZfctffTGvwsA4JcJ1IRk8b2tg+hz44f0lngvRTeHvp3Kyix9ACgudHH9aQ== +jest-resolve-dependencies@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz#a6a329708a128e68d67c49f38678a4a4a914c3bf" + integrity sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA== dependencies: jest-regex-util "^29.2.0" - jest-snapshot "^29.2.2" + jest-snapshot "^29.3.1" -jest-resolve@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest-resolve/-/jest-resolve-29.2.2.tgz#ad6436053b0638b41e12bbddde2b66e1397b35b5" - integrity sha512-3gaLpiC3kr14rJR3w7vWh0CBX2QAhfpfiQTwrFPvVrcHe5VUBtIXaR004aWE/X9B2CFrITOQAp5gxLONGrk6GA== +jest-resolve@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.3.1.tgz#9a4b6b65387a3141e4a40815535c7f196f1a68a7" + integrity sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.2.1" + jest-haste-map "^29.3.1" jest-pnp-resolver "^1.2.2" - jest-util "^29.2.1" - jest-validate "^29.2.2" + jest-util "^29.3.1" + jest-validate "^29.3.1" resolve "^1.20.0" resolve.exports "^1.1.0" slash "^3.0.0" -jest-runner@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest-runner/-/jest-runner-29.2.2.tgz#6b5302ed15eba8bf05e6b14d40f1e8d469564da3" - integrity sha512-1CpUxXDrbsfy9Hr9/1zCUUhT813kGGK//58HeIw/t8fa/DmkecEwZSWlb1N/xDKXg3uCFHQp1GCvlSClfImMxg== - dependencies: - "@jest/console" "^29.2.1" - "@jest/environment" "^29.2.2" - "@jest/test-result" "^29.2.1" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" +jest-runner@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-29.3.1.tgz#a92a879a47dd096fea46bb1517b0a99418ee9e2d" + integrity sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA== + dependencies: + "@jest/console" "^29.3.1" + "@jest/environment" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" jest-docblock "^29.2.0" - jest-environment-node "^29.2.2" - jest-haste-map "^29.2.1" - jest-leak-detector "^29.2.1" - jest-message-util "^29.2.1" - jest-resolve "^29.2.2" - jest-runtime "^29.2.2" - jest-util "^29.2.1" - jest-watcher "^29.2.2" - jest-worker "^29.2.1" + jest-environment-node "^29.3.1" + jest-haste-map "^29.3.1" + jest-leak-detector "^29.3.1" + jest-message-util "^29.3.1" + jest-resolve "^29.3.1" + jest-runtime "^29.3.1" + jest-util "^29.3.1" + jest-watcher "^29.3.1" + jest-worker "^29.3.1" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest-runtime/-/jest-runtime-29.2.2.tgz#4068ee82423769a481460efd21d45a8efaa5c179" - integrity sha512-TpR1V6zRdLynckKDIQaY41od4o0xWL+KOPUCZvJK2bu5P1UXhjobt5nJ2ICNeIxgyj9NGkO0aWgDqYPVhDNKjA== +jest-runtime@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.3.1.tgz#21efccb1a66911d6d8591276a6182f520b86737a" + integrity sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A== dependencies: - "@jest/environment" "^29.2.2" - "@jest/fake-timers" "^29.2.2" - "@jest/globals" "^29.2.2" + "@jest/environment" "^29.3.1" + "@jest/fake-timers" "^29.3.1" + "@jest/globals" "^29.3.1" "@jest/source-map" "^29.2.0" - "@jest/test-result" "^29.2.1" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/test-result" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.2.1" - jest-message-util "^29.2.1" - jest-mock "^29.2.2" + jest-haste-map "^29.3.1" + jest-message-util "^29.3.1" + jest-mock "^29.3.1" jest-regex-util "^29.2.0" - jest-resolve "^29.2.2" - jest-snapshot "^29.2.2" - jest-util "^29.2.1" + jest-resolve "^29.3.1" + jest-snapshot "^29.3.1" + jest-util "^29.3.1" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest-snapshot/-/jest-snapshot-29.2.2.tgz#1016ce60297b77382386bad561107174604690c2" - integrity sha512-GfKJrpZ5SMqhli3NJ+mOspDqtZfJBryGA8RIBxF+G+WbDoC7HCqKaeAss4Z/Sab6bAW11ffasx8/vGsj83jyjA== +jest-snapshot@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.3.1.tgz#17bcef71a453adc059a18a32ccbd594b8cc4e45e" + integrity sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" @@ -3674,23 +3689,23 @@ jest-snapshot@^29.2.2: "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.2.2" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/expect-utils" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@types/babel__traverse" "^7.0.6" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.2.2" + expect "^29.3.1" graceful-fs "^4.2.9" - jest-diff "^29.2.1" + jest-diff "^29.3.1" jest-get-type "^29.2.0" - jest-haste-map "^29.2.1" - jest-matcher-utils "^29.2.2" - jest-message-util "^29.2.1" - jest-util "^29.2.1" + jest-haste-map "^29.3.1" + jest-matcher-utils "^29.3.1" + jest-message-util "^29.3.1" + jest-util "^29.3.1" natural-compare "^1.4.0" - pretty-format "^29.2.1" + pretty-format "^29.3.1" semver "^7.3.5" jest-util@^29.0.0, jest-util@^29.0.3: @@ -3705,42 +3720,42 @@ jest-util@^29.0.0, jest-util@^29.0.3: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^29.2.1: - version "29.2.1" - resolved "https://registry.npmmirror.com/jest-util/-/jest-util-29.2.1.tgz#f26872ba0dc8cbefaba32c34f98935f6cf5fc747" - integrity sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g== +jest-util@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz#1dda51e378bbcb7e3bc9d8ab651445591ed373e1" + integrity sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest-validate/-/jest-validate-29.2.2.tgz#e43ce1931292dfc052562a11bc681af3805eadce" - integrity sha512-eJXATaKaSnOuxNfs8CLHgdABFgUrd0TtWS8QckiJ4L/QVDF4KVbZFBBOwCBZHOS0Rc5fOxqngXeGXE3nGQkpQA== +jest-validate@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-29.3.1.tgz#d56fefaa2e7d1fde3ecdc973c7f7f8f25eea704a" + integrity sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" camelcase "^6.2.0" chalk "^4.0.0" jest-get-type "^29.2.0" leven "^3.1.0" - pretty-format "^29.2.1" + pretty-format "^29.3.1" -jest-watcher@^29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest-watcher/-/jest-watcher-29.2.2.tgz#7093d4ea8177e0a0da87681a9e7b09a258b9daf7" - integrity sha512-j2otfqh7mOvMgN2WlJ0n7gIx9XCMWntheYGlBK7+5g3b1Su13/UAK7pdKGyd4kDlrLwtH2QPvRv5oNIxWvsJ1w== +jest-watcher@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.3.1.tgz#3341547e14fe3c0f79f9c3a4c62dbc3fc977fd4a" + integrity sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg== dependencies: - "@jest/test-result" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/test-result" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.2.1" + jest-util "^29.3.1" string-length "^4.0.1" jest-worker@^27.4.5: @@ -3752,25 +3767,25 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.2.1: - version "29.2.1" - resolved "https://registry.npmmirror.com/jest-worker/-/jest-worker-29.2.1.tgz#8ba68255438252e1674f990f0180c54dfa26a3b1" - integrity sha512-ROHTZ+oj7sBrgtv46zZ84uWky71AoYi0vEV9CdEtc1FQunsoAGe5HbQmW76nI5QWdvECVPrSi1MCVUmizSavMg== +jest-worker@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz#e9462161017a9bb176380d721cab022661da3d6b" + integrity sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw== dependencies: "@types/node" "*" - jest-util "^29.2.1" + jest-util "^29.3.1" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@29.2.2: - version "29.2.2" - resolved "https://registry.npmmirror.com/jest/-/jest-29.2.2.tgz#24da83cbbce514718acd698926b7679109630476" - integrity sha512-r+0zCN9kUqoON6IjDdjbrsWobXM/09Nd45kIPRD8kloaRh1z5ZCMdVsgLXGxmlL7UpAJsvCYOQNO+NjvG/gqiQ== +jest@29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz#c130c0d551ae6b5459b8963747fed392ddbde122" + integrity sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA== dependencies: - "@jest/core" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/core" "^29.3.1" + "@jest/types" "^29.3.1" import-local "^3.0.2" - jest-cli "^29.2.2" + jest-cli "^29.3.1" js-sdsl@^4.1.4: version "4.1.4" @@ -4564,10 +4579,10 @@ pretty-format@^29.0.0, pretty-format@^29.0.3: ansi-styles "^5.0.0" react-is "^18.0.0" -pretty-format@^29.2.1: - version "29.2.1" - resolved "https://registry.npmmirror.com/pretty-format/-/pretty-format-29.2.1.tgz#86e7748fe8bbc96a6a4e04fa99172630907a9611" - integrity sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA== +pretty-format@^29.3.1: + version "29.3.1" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz#1841cac822b02b4da8971dacb03e8a871b4722da" + integrity sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg== dependencies: "@jest/schemas" "^29.0.0" ansi-styles "^5.0.0" @@ -5334,16 +5349,21 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.4.0, tslib@^2.1.0, tslib@^2.3.1: - version "2.4.0" - resolved "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@2.4.1: + version "2.4.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.1.0, tslib@^2.3.1: + version "2.4.0" + resolved "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.npmmirror.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"