From 24790e8985e9eb89e39a11149762ab13b5baaa3f Mon Sep 17 00:00:00 2001 From: liuyun Date: Sat, 10 Oct 2026 21:39:37 +0800 Subject: [PATCH] feat: add Swagger API docs (/docs) with DTO schemas Co-Authored-By: Claude Opus 4.8 --- package.json | 1 + pnpm-lock.yaml | 98 ++++++++++++++++++++++++++++++++ src/auth/dto.ts | 5 ++ src/auth/jwt-auth.guard.ts | 3 + src/common/page.dto.ts | 4 ++ src/configs/dto.ts | 29 ++++++++-- src/dicts/dto.ts | 81 +++++++++++++++++++++----- src/main.ts | 10 ++++ src/orgs/dto.ts | 59 +++++++++++++++---- src/resources/dto.ts | 113 ++++++++++++++++++++++++++++++------- src/roles/dto.ts | 52 ++++++++++++++--- src/tenants/dto.ts | 41 +++++++++++--- src/users/dto.ts | 82 ++++++++++++++++++++++----- 13 files changed, 500 insertions(+), 78 deletions(-) diff --git a/package.json b/package.json index f10dafe..c33bfba 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@nestjs/core": "^12.0.1", "@nestjs/jwt": "^12.0.2", "@nestjs/platform-express": "^12.0.1", + "@nestjs/swagger": "^12.0.2", "@prisma/client": "^6.19.3", "bcryptjs": "^3.0.3", "class-transformer": "^0.5.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9a90c9..c1b9d16 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,6 +178,9 @@ importers: '@nestjs/platform-express': specifier: ^12.0.1 version: 12.1.2(@nestjs/common@12.1.2(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@7.2.0))(@nestjs/core@12.1.2)(supports-color@7.2.0) + '@nestjs/swagger': + specifier: ^12.0.2 + version: 12.0.2(@nestjs/common@12.1.2(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@7.2.0))(@nestjs/core@12.1.2)(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(typescript@6.0.3) '@prisma/client': specifier: ^6.19.3 version: 6.19.3(prisma@6.19.3(typescript@6.0.3))(typescript@6.0.3) @@ -616,6 +619,9 @@ packages: resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} + '@microsoft/tsdoc@0.17.0': + resolution: {integrity: sha512-p68VexhnH7ojf3U27RdryUDqnKJgKaWUbCL96vJR7N0mqDuhKG5OIECk3sCPPdynxjk+yoVhRkScqtv5KQjzsw==} + '@nestjs/cli@12.0.8': resolution: {integrity: sha512-iqm+KXb8cnpnlD4hMBf1w2/4i1GSs0DCLI7oyP4MTmwf9KmjTG1SpAjkl/D+kKuUToycBrMF4+eSwyPVdTMGKQ==} engines: {node: '>= 20.11'} @@ -683,6 +689,20 @@ packages: peerDependencies: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 + '@nestjs/mapped-types@12.0.0': + resolution: {integrity: sha512-aX7lxZcqXldtnSr0bgT2ZPQvpTpIwBMW1GMX29o4s3q02vmq6JoTYuPW0GwYXa8BiFMRX7WbGZVDA703pNqOMQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 || ^12.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 || ^0.15.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + '@nestjs/mau@0.2.8': resolution: {integrity: sha512-nIb0i3c/KhKEbxZfYMEKv9Mcvk5miBdZcexnvkgnAUhoMO2BUjOGT+Khf2cZzu7B51Y7v01NG2JVl/HdQEUQ6w==} engines: {node: '>= 20.0.0'} @@ -704,6 +724,27 @@ packages: prettier: optional: true + '@nestjs/swagger@12.0.2': + resolution: {integrity: sha512-/IDj6o2k1wqZybhIkMaJp58YNrAos/4TwTf3zBn/j1flKpMb5Tyfbj9sP6rpLucQ7PzHHu7n4ucmeHWEisfn6A==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@fastify/static': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/common': ^12.0.0 + '@nestjs/core': ^12.0.0 + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + typescript: ^5.5.0 || ^6.0.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + class-transformer: + optional: true + class-validator: + optional: true + typescript: + optional: true + '@nestjs/testing@12.1.2': resolution: {integrity: sha512-JgetYeGHGK6iUEztXyiQLOfYLWqUgAyexLMzhuAMasgLiDgYShh/PoRESE9tzpRZo4eWKme3uSZ9DFR6Myuojw==} peerDependencies: @@ -1011,6 +1052,9 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@scarf/scarf@1.4.0': + resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} + '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -1160,6 +1204,9 @@ packages: append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + array-timsort@1.0.3: resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} @@ -1474,6 +1521,9 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-toolkit@1.52.0: + resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==} + esbuild@0.28.2: resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} engines: {node: '>=18'} @@ -1698,6 +1748,10 @@ packages: js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-yaml@5.4.2: + resolution: {integrity: sha512-m+aqu+LwO1O6sIopafj8HUVl5aawITwZQe/yHpMCKjaWBaA/d07B/QdMb3529REftiU+RMMHL3Vlsw3hON7vWg==} + hasBin: true + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -2284,6 +2338,9 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + swagger-ui-dist@5.33.0: + resolution: {integrity: sha512-wpdK+m6BU5yj6pmUdMskZVTSWYG4DLglAx3sIhylloY37i8O37IrH+YEpqdXNfpaTGxILRBFzUqLF2jKqbfI7A==} + thread-stream@4.2.0: resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} engines: {node: '>=20'} @@ -2762,6 +2819,8 @@ snapshots: '@lukeed/csprng@1.1.0': {} + '@microsoft/tsdoc@0.17.0': {} + '@nestjs/cli@12.0.8(@types/node@24.19.1)(prettier@3.9.9)': dependencies: '@angular-devkit/core': 22.2.0(chokidar@5.0.0) @@ -2818,6 +2877,14 @@ snapshots: '@types/jsonwebtoken': 9.0.10 jsonwebtoken: 9.0.3 + '@nestjs/mapped-types@12.0.0(@nestjs/common@12.1.2(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@7.2.0))(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)': + dependencies: + '@nestjs/common': 12.1.2(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@7.2.0) + reflect-metadata: 0.2.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.15.1 + '@nestjs/mau@0.2.8': dependencies: chalk: 4.1.2 @@ -2853,6 +2920,23 @@ snapshots: transitivePeerDependencies: - chokidar + '@nestjs/swagger@12.0.2(@nestjs/common@12.1.2(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@7.2.0))(@nestjs/core@12.1.2)(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(typescript@6.0.3)': + dependencies: + '@microsoft/tsdoc': 0.17.0 + '@nestjs/common': 12.1.2(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@7.2.0) + '@nestjs/core': 12.1.2(@nestjs/common@12.1.2(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@7.2.0))(@nestjs/platform-express@12.1.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/mapped-types': 12.0.0(@nestjs/common@12.1.2(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@7.2.0))(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2) + '@standard-schema/spec': 1.1.0 + es-toolkit: 1.52.0 + js-yaml: 5.4.2 + path-to-regexp: 8.4.2 + reflect-metadata: 0.2.2 + swagger-ui-dist: 5.33.0 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.15.1 + typescript: 6.0.3 + '@nestjs/testing@12.1.2(@nestjs/common@12.1.2(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@7.2.0))(@nestjs/core@12.1.2)(@nestjs/platform-express@12.1.2)': dependencies: '@nestjs/common': 12.1.2(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@7.2.0) @@ -3028,6 +3112,8 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} + '@scarf/scarf@1.4.0': {} + '@sindresorhus/is@4.6.0': {} '@standard-schema/spec@1.1.0': {} @@ -3207,6 +3293,8 @@ snapshots: append-field@1.0.0: {} + argparse@2.0.1: {} + array-timsort@1.0.3: {} asap@2.0.6: {} @@ -3479,6 +3567,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.4 + es-toolkit@1.52.0: {} + esbuild@0.28.2: optionalDependencies: '@esbuild/aix-ppc64': 0.28.2 @@ -3755,6 +3845,10 @@ snapshots: js-tokens@10.0.0: {} + js-yaml@5.4.2: + dependencies: + argparse: 2.0.1 + json-schema-traverse@1.0.0: {} json5@2.2.3: {} @@ -4364,6 +4458,10 @@ snapshots: dependencies: has-flag: 4.0.0 + swagger-ui-dist@5.33.0: + dependencies: + '@scarf/scarf': 1.4.0 + thread-stream@4.2.0: dependencies: real-require: 1.0.0 diff --git a/src/auth/dto.ts b/src/auth/dto.ts index 207ccc0..53b184b 100644 --- a/src/auth/dto.ts +++ b/src/auth/dto.ts @@ -1,20 +1,25 @@ +import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; export class LoginDto { + @ApiProperty({ description: '租户编码' }) @IsString() @IsNotEmpty() tenantCode!: string; + @ApiProperty({ description: '用户名' }) @IsString() @IsNotEmpty() username!: string; + @ApiProperty({ description: '密码' }) @IsString() @IsNotEmpty() password!: string; } export class RefreshDto { + @ApiProperty({ description: '刷新令牌' }) @IsString() @IsNotEmpty() refreshToken!: string; diff --git a/src/auth/jwt-auth.guard.ts b/src/auth/jwt-auth.guard.ts index 82d8628..031085e 100644 --- a/src/auth/jwt-auth.guard.ts +++ b/src/auth/jwt-auth.guard.ts @@ -24,6 +24,9 @@ export class JwtAuthGuard implements CanActivate { if (isPublic) return true; const request = context.switchToHttp().getRequest(); + const url: string = request.originalUrl ?? request.url ?? ''; + if (url.startsWith('/docs')) return true; + const token = this.extractToken(request); if (!token) throw new UnauthorizedException('未登录'); try { diff --git a/src/common/page.dto.ts b/src/common/page.dto.ts index 420b108..0b6fdac 100644 --- a/src/common/page.dto.ts +++ b/src/common/page.dto.ts @@ -1,13 +1,16 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsInt, IsOptional, Min } from 'class-validator'; export class PageQuery { + @ApiPropertyOptional({ description: '页码(从 1 开始)', default: 1 }) @IsOptional() @Type(() => Number) @IsInt() @Min(1) page = 1; + @ApiPropertyOptional({ description: '每页条数', default: 20 }) @IsOptional() @Type(() => Number) @IsInt() @@ -16,6 +19,7 @@ export class PageQuery { } export class IdParam { + @ApiPropertyOptional({ description: '记录 ID' }) @IsOptional() id!: string; } diff --git a/src/configs/dto.ts b/src/configs/dto.ts index 61070f1..af8a027 100644 --- a/src/configs/dto.ts +++ b/src/configs/dto.ts @@ -1,12 +1,31 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class CreateConfigDto { - @IsString() @IsNotEmpty() key!: string; - @IsOptional() @IsString() value?: string; - @IsOptional() @IsString() remark?: string; + @ApiProperty({ description: '配置 key(租户内唯一)' }) + @IsString() + @IsNotEmpty() + key!: string; + + @ApiPropertyOptional({ description: '配置值' }) + @IsOptional() + @IsString() + value?: string; + + @ApiPropertyOptional({ description: '备注' }) + @IsOptional() + @IsString() + remark?: string; } export class UpdateConfigDto { - @IsOptional() @IsString() value?: string; - @IsOptional() @IsString() remark?: string; + @ApiPropertyOptional({ description: '配置值' }) + @IsOptional() + @IsString() + value?: string; + + @ApiPropertyOptional({ description: '备注' }) + @IsOptional() + @IsString() + remark?: string; } diff --git a/src/dicts/dto.ts b/src/dicts/dto.ts index c91e7a3..16fb723 100644 --- a/src/dicts/dto.ts +++ b/src/dicts/dto.ts @@ -1,27 +1,80 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class CreateDictTypeDto { - @IsString() @IsNotEmpty() code!: string; - @IsString() @IsNotEmpty() name!: string; - @IsOptional() @IsString() remark?: string; + @ApiProperty({ description: '字典类型编码' }) + @IsString() + @IsNotEmpty() + code!: string; + + @ApiProperty({ description: '字典类型名称' }) + @IsString() + @IsNotEmpty() + name!: string; + + @ApiPropertyOptional({ description: '备注' }) + @IsOptional() + @IsString() + remark?: string; } export class UpdateDictTypeDto { - @IsOptional() @IsString() name?: string; - @IsOptional() @IsString() status?: string; - @IsOptional() @IsString() remark?: string; + @ApiPropertyOptional({ description: '字典类型名称' }) + @IsOptional() + @IsString() + name?: string; + + @ApiPropertyOptional({ description: '状态:enabled/disabled' }) + @IsOptional() + @IsString() + status?: string; + + @ApiPropertyOptional({ description: '备注' }) + @IsOptional() + @IsString() + remark?: string; } export class CreateDictItemDto { - @IsString() @IsNotEmpty() dictTypeCode!: string; - @IsString() @IsNotEmpty() label!: string; - @IsString() @IsNotEmpty() value!: string; - @IsOptional() @IsInt() sort?: number; + @ApiProperty({ description: '所属字典类型 code' }) + @IsString() + @IsNotEmpty() + dictTypeCode!: string; + + @ApiProperty({ description: '标签(显示名)' }) + @IsString() + @IsNotEmpty() + label!: string; + + @ApiProperty({ description: '值' }) + @IsString() + @IsNotEmpty() + value!: string; + + @ApiPropertyOptional({ description: '排序', default: 0 }) + @IsOptional() + @IsInt() + sort?: number; } export class UpdateDictItemDto { - @IsOptional() @IsString() label?: string; - @IsOptional() @IsString() value?: string; - @IsOptional() @IsInt() sort?: number; - @IsOptional() @IsString() status?: string; + @ApiPropertyOptional({ description: '标签' }) + @IsOptional() + @IsString() + label?: string; + + @ApiPropertyOptional({ description: '值' }) + @IsOptional() + @IsString() + value?: string; + + @ApiPropertyOptional({ description: '排序' }) + @IsOptional() + @IsInt() + sort?: number; + + @ApiPropertyOptional({ description: '状态:enabled/disabled' }) + @IsOptional() + @IsString() + status?: string; } diff --git a/src/main.ts b/src/main.ts index e8ee779..30a4efd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,6 +2,7 @@ import 'dotenv/config'; import { NestFactory } from '@nestjs/core'; import { ValidationPipe } from '@nestjs/common'; import { Logger } from 'nestjs-pino'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { AppModule } from './app.module.js'; async function bootstrap(): Promise { @@ -10,6 +11,15 @@ async function bootstrap(): Promise { app.enableCors(); app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true })); app.useLogger(app.get(Logger)); + + const swaggerConfig = new DocumentBuilder() + .setTitle('sysmgr API') + .setDescription('通用系统管理脚手架 API 文档') + .setVersion('1.0') + .addBearerAuth() + .build(); + SwaggerModule.setup('docs', app, SwaggerModule.createDocument(app, swaggerConfig)); + await app.listen(process.env.PORT ?? 3000); } await bootstrap(); diff --git a/src/orgs/dto.ts b/src/orgs/dto.ts index 15482c1..65d1251 100644 --- a/src/orgs/dto.ts +++ b/src/orgs/dto.ts @@ -1,17 +1,56 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class CreateOrgDto { - @IsString() @IsNotEmpty() code!: string; - @IsString() @IsNotEmpty() name!: string; - @IsOptional() @IsString() parentCode?: string; - @IsOptional() @IsString() leader?: string; - @IsOptional() @IsInt() sort?: number; + @ApiProperty({ description: '组织 code(租户内唯一)' }) + @IsString() + @IsNotEmpty() + code!: string; + + @ApiProperty({ description: '组织名称' }) + @IsString() + @IsNotEmpty() + name!: string; + + @ApiPropertyOptional({ description: '父组织 code' }) + @IsOptional() + @IsString() + parentCode?: string; + + @ApiPropertyOptional({ description: '负责人' }) + @IsOptional() + @IsString() + leader?: string; + + @ApiPropertyOptional({ description: '排序', default: 0 }) + @IsOptional() + @IsInt() + sort?: number; } export class UpdateOrgDto { - @IsOptional() @IsString() name?: string; - @IsOptional() @IsString() parentCode?: string; - @IsOptional() @IsString() leader?: string; - @IsOptional() @IsInt() sort?: number; - @IsOptional() @IsString() status?: string; + @ApiPropertyOptional({ description: '组织名称' }) + @IsOptional() + @IsString() + name?: string; + + @ApiPropertyOptional({ description: '父组织 code' }) + @IsOptional() + @IsString() + parentCode?: string; + + @ApiPropertyOptional({ description: '负责人' }) + @IsOptional() + @IsString() + leader?: string; + + @ApiPropertyOptional({ description: '排序' }) + @IsOptional() + @IsInt() + sort?: number; + + @ApiPropertyOptional({ description: '状态:enabled/disabled' }) + @IsOptional() + @IsString() + status?: string; } diff --git a/src/resources/dto.ts b/src/resources/dto.ts index 71741c4..816f48b 100644 --- a/src/resources/dto.ts +++ b/src/resources/dto.ts @@ -1,26 +1,101 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class CreateResourceDto { - @IsString() @IsNotEmpty() type!: string; - @IsString() @IsNotEmpty() code!: string; - @IsString() @IsNotEmpty() name!: string; - @IsOptional() @IsString() parentCode?: string; - @IsOptional() @IsString() path?: string; - @IsOptional() @IsString() component?: string; - @IsOptional() @IsString() method?: string; - @IsOptional() @IsString() apiPath?: string; - @IsOptional() @IsString() icon?: string; - @IsOptional() @IsInt() sort?: number; + @ApiProperty({ description: '类型:menu/button/api' }) + @IsString() + @IsNotEmpty() + type!: string; + + @ApiProperty({ description: '资源 code(租户内唯一,兼权限标识)' }) + @IsString() + @IsNotEmpty() + code!: string; + + @ApiProperty({ description: '名称' }) + @IsString() + @IsNotEmpty() + name!: string; + + @ApiPropertyOptional({ description: '父资源 code' }) + @IsOptional() + @IsString() + parentCode?: string; + + @ApiPropertyOptional({ description: '路由 path(menu)' }) + @IsOptional() + @IsString() + path?: string; + + @ApiPropertyOptional({ description: '前端组件(menu)' }) + @IsOptional() + @IsString() + component?: string; + + @ApiPropertyOptional({ description: 'HTTP method(api)' }) + @IsOptional() + @IsString() + method?: string; + + @ApiPropertyOptional({ description: '接口 path(api)' }) + @IsOptional() + @IsString() + apiPath?: string; + + @ApiPropertyOptional({ description: '图标' }) + @IsOptional() + @IsString() + icon?: string; + + @ApiPropertyOptional({ description: '排序', default: 0 }) + @IsOptional() + @IsInt() + sort?: number; } export class UpdateResourceDto { - @IsOptional() @IsString() name?: string; - @IsOptional() @IsString() parentCode?: string; - @IsOptional() @IsString() path?: string; - @IsOptional() @IsString() component?: string; - @IsOptional() @IsString() method?: string; - @IsOptional() @IsString() apiPath?: string; - @IsOptional() @IsString() icon?: string; - @IsOptional() @IsInt() sort?: number; - @IsOptional() @IsString() status?: string; + @ApiPropertyOptional({ description: '名称' }) + @IsOptional() + @IsString() + name?: string; + + @ApiPropertyOptional({ description: '父资源 code' }) + @IsOptional() + @IsString() + parentCode?: string; + + @ApiPropertyOptional({ description: '路由 path' }) + @IsOptional() + @IsString() + path?: string; + + @ApiPropertyOptional({ description: '前端组件' }) + @IsOptional() + @IsString() + component?: string; + + @ApiPropertyOptional({ description: 'HTTP method' }) + @IsOptional() + @IsString() + method?: string; + + @ApiPropertyOptional({ description: '接口 path' }) + @IsOptional() + @IsString() + apiPath?: string; + + @ApiPropertyOptional({ description: '图标' }) + @IsOptional() + @IsString() + icon?: string; + + @ApiPropertyOptional({ description: '排序' }) + @IsOptional() + @IsInt() + sort?: number; + + @ApiPropertyOptional({ description: '状态:enabled/disabled' }) + @IsOptional() + @IsString() + status?: string; } diff --git a/src/roles/dto.ts b/src/roles/dto.ts index 14a7239..f9fee1a 100644 --- a/src/roles/dto.ts +++ b/src/roles/dto.ts @@ -1,19 +1,53 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class CreateRoleDto { - @IsString() @IsNotEmpty() code!: string; - @IsString() @IsNotEmpty() name!: string; - @IsOptional() @IsString() dataScope?: string; - @IsOptional() @IsString() remark?: string; + @ApiProperty({ description: '角色编码' }) + @IsString() + @IsNotEmpty() + code!: string; + + @ApiProperty({ description: '角色名称' }) + @IsString() + @IsNotEmpty() + name!: string; + + @ApiPropertyOptional({ description: '数据权限:all/dept/dept_and_child/self/custom', default: 'all' }) + @IsOptional() + @IsString() + dataScope?: string; + + @ApiPropertyOptional({ description: '备注' }) + @IsOptional() + @IsString() + remark?: string; } export class UpdateRoleDto { - @IsOptional() @IsString() name?: string; - @IsOptional() @IsString() dataScope?: string; - @IsOptional() @IsString() status?: string; - @IsOptional() @IsString() remark?: string; + @ApiPropertyOptional({ description: '角色名称' }) + @IsOptional() + @IsString() + name?: string; + + @ApiPropertyOptional({ description: '数据权限:all/dept/dept_and_child/self/custom' }) + @IsOptional() + @IsString() + dataScope?: string; + + @ApiPropertyOptional({ description: '状态:enabled/disabled' }) + @IsOptional() + @IsString() + status?: string; + + @ApiPropertyOptional({ description: '备注' }) + @IsOptional() + @IsString() + remark?: string; } export class AssignResourcesDto { - @IsArray() @IsString({ each: true }) resourceCodes!: string[]; + @ApiProperty({ description: '资源 code 列表', type: [String] }) + @IsArray() + @IsString({ each: true }) + resourceCodes!: string[]; } diff --git a/src/tenants/dto.ts b/src/tenants/dto.ts index bacdcf7..ccf39bc 100644 --- a/src/tenants/dto.ts +++ b/src/tenants/dto.ts @@ -1,14 +1,41 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class CreateTenantDto { - @IsString() @IsNotEmpty() code!: string; - @IsString() @IsNotEmpty() name!: string; - @IsOptional() @IsString() status?: string; - @IsOptional() @IsString() remark?: string; + @ApiProperty({ description: '租户编码' }) + @IsString() + @IsNotEmpty() + code!: string; + + @ApiProperty({ description: '租户名称' }) + @IsString() + @IsNotEmpty() + name!: string; + + @ApiPropertyOptional({ description: '状态:enabled/disabled', default: 'enabled' }) + @IsOptional() + @IsString() + status?: string; + + @ApiPropertyOptional({ description: '备注' }) + @IsOptional() + @IsString() + remark?: string; } export class UpdateTenantDto { - @IsOptional() @IsString() name?: string; - @IsOptional() @IsString() status?: string; - @IsOptional() @IsString() remark?: string; + @ApiPropertyOptional({ description: '租户名称' }) + @IsOptional() + @IsString() + name?: string; + + @ApiPropertyOptional({ description: '状态:enabled/disabled' }) + @IsOptional() + @IsString() + status?: string; + + @ApiPropertyOptional({ description: '备注' }) + @IsOptional() + @IsString() + remark?: string; } diff --git a/src/users/dto.ts b/src/users/dto.ts index 79d86c6..f455d8e 100644 --- a/src/users/dto.ts +++ b/src/users/dto.ts @@ -1,24 +1,78 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class CreateUserDto { - @IsString() @IsNotEmpty() username!: string; - @IsString() @IsNotEmpty() password!: string; - @IsOptional() @IsString() nickname?: string; - @IsOptional() @IsString() email?: string; - @IsOptional() @IsString() phone?: string; - @IsOptional() @IsString() avatar?: string; - @IsOptional() @IsString() orgCode?: string; + @ApiProperty({ description: '用户名' }) + @IsString() + @IsNotEmpty() + username!: string; + + @ApiProperty({ description: '初始密码' }) + @IsString() + @IsNotEmpty() + password!: string; + + @ApiPropertyOptional({ description: '昵称' }) + @IsOptional() + @IsString() + nickname?: string; + + @ApiPropertyOptional({ description: '邮箱' }) + @IsOptional() + @IsString() + email?: string; + + @ApiPropertyOptional({ description: '手机号' }) + @IsOptional() + @IsString() + phone?: string; + + @ApiPropertyOptional({ description: '头像' }) + @IsOptional() + @IsString() + avatar?: string; + + @ApiPropertyOptional({ description: '所属部门 code' }) + @IsOptional() + @IsString() + orgCode?: string; } export class UpdateUserDto { - @IsOptional() @IsString() nickname?: string; - @IsOptional() @IsString() email?: string; - @IsOptional() @IsString() phone?: string; - @IsOptional() @IsString() avatar?: string; - @IsOptional() @IsString() orgCode?: string; - @IsOptional() @IsString() status?: string; + @ApiPropertyOptional({ description: '昵称' }) + @IsOptional() + @IsString() + nickname?: string; + + @ApiPropertyOptional({ description: '邮箱' }) + @IsOptional() + @IsString() + email?: string; + + @ApiPropertyOptional({ description: '手机号' }) + @IsOptional() + @IsString() + phone?: string; + + @ApiPropertyOptional({ description: '头像' }) + @IsOptional() + @IsString() + avatar?: string; + + @ApiPropertyOptional({ description: '所属部门 code' }) + @IsOptional() + @IsString() + orgCode?: string; + + @ApiPropertyOptional({ description: '状态:enabled/disabled/locked' }) + @IsOptional() + @IsString() + status?: string; } export class ResetPasswordDto { - @IsString() @IsNotEmpty() password!: string; + @ApiProperty({ description: '新密码' }) + @IsString() + @IsNotEmpty() + password!: string; }