Skip to content

Commit 7c7812c

Browse files
authored
Merge pull request #203 from KuchiMercy/issue-#122
Inefficient Data Transfer Objects
2 parents fe1b818 + 72e9216 commit 7c7812c

File tree

6 files changed

+679
-49
lines changed

6 files changed

+679
-49
lines changed

src/auth/dto/auth-response.dto.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,139 @@
11
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2+
import { Exclude, Expose, Type } from 'class-transformer';
3+
import { IsString, IsOptional, IsNumber, IsArray, IsEmail, ValidateNested, ArrayMinSize } from 'class-validator';
24

5+
@Exclude()
36
export class TokenPairDto {
7+
@Expose()
48
@ApiProperty({
59
description: 'JWT access token',
610
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
711
})
12+
@IsString()
813
accessToken: string;
914

15+
@Expose()
1016
@ApiProperty({
1117
description: 'JWT refresh token',
1218
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
1319
})
20+
@IsString()
1421
refreshToken: string;
1522

23+
@Expose()
1624
@ApiProperty({
1725
description: 'Token expiration time in seconds',
1826
example: 3600,
1927
})
28+
@IsNumber()
2029
expiresIn: number;
2130

31+
@Expose()
2232
@ApiProperty({
2333
description: 'Token type',
2434
example: 'Bearer',
2535
})
36+
@IsString()
2637
tokenType: string;
38+
39+
constructor(partial: Partial<TokenPairDto>) {
40+
Object.assign(this, partial);
41+
}
2742
}
2843

44+
@Exclude()
2945
export class AuthUserDto {
46+
@Expose()
3047
@ApiProperty({ example: 'user_abc123' })
48+
@IsString()
3149
id: string;
3250

51+
@Expose()
3352
@ApiProperty({ example: '[email protected]' })
53+
@IsEmail()
3454
email: string;
3555

56+
@Expose()
3657
@ApiPropertyOptional({ example: 'John' })
58+
@IsOptional()
59+
@IsString()
3760
firstName?: string;
3861

62+
@Expose()
3963
@ApiPropertyOptional({ example: 'Doe' })
64+
@IsOptional()
65+
@IsString()
4066
lastName?: string;
4167

68+
@Expose()
4269
@ApiPropertyOptional({ example: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' })
70+
@IsOptional()
71+
@IsString()
4372
walletAddress?: string;
4473

74+
@Expose()
4575
@ApiProperty({ example: ['user'] })
76+
@IsArray()
77+
@ArrayMinSize(1)
78+
@IsString({ each: true })
4679
roles: string[];
80+
81+
constructor(partial: Partial<AuthUserDto>) {
82+
Object.assign(this, partial);
83+
}
4784
}
4885

86+
@Exclude()
4987
export class LoginResponseDto {
88+
@Expose()
5089
@ApiProperty({ type: AuthUserDto })
90+
@ValidateNested()
91+
@Type(() => AuthUserDto)
5192
user: AuthUserDto;
5293

94+
@Expose()
5395
@ApiProperty({ type: TokenPairDto })
96+
@ValidateNested()
97+
@Type(() => TokenPairDto)
5498
tokens: TokenPairDto;
99+
100+
constructor(partial: Partial<LoginResponseDto>) {
101+
Object.assign(this, partial);
102+
}
55103
}
56104

105+
@Exclude()
57106
export class RegisterResponseDto {
107+
@Expose()
58108
@ApiProperty({ type: AuthUserDto })
109+
@ValidateNested()
110+
@Type(() => AuthUserDto)
59111
user: AuthUserDto;
60112

113+
@Expose()
61114
@ApiProperty({
62115
description: 'Message about email verification',
63116
example: 'Please check your email to verify your account',
64117
})
118+
@IsString()
65119
message: string;
120+
121+
constructor(partial: Partial<RegisterResponseDto>) {
122+
Object.assign(this, partial);
123+
}
66124
}
67125

126+
@Exclude()
68127
export class MessageResponseDto {
128+
@Expose()
69129
@ApiProperty({
70130
description: 'Response message',
71131
example: 'Operation completed successfully',
72132
})
133+
@IsString()
73134
message: string;
135+
136+
constructor(partial: Partial<MessageResponseDto>) {
137+
Object.assign(this, partial);
138+
}
74139
}

src/common/dtos/api-response.dto.ts

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,80 @@
11
import { ApiProperty } from '@nestjs/swagger';
2+
import { Exclude, Expose, Transform, Type } from 'class-transformer';
3+
import { IsBoolean, IsNumber, IsString, IsOptional, IsArray, ValidateNested, IsObject } from 'class-validator';
4+
5+
/**
6+
* Error details structure
7+
*/
8+
@Exclude()
9+
export class ErrorDetailsDto {
10+
@Expose()
11+
@ApiProperty({ description: 'Error code', example: 'VALIDATION_ERROR' })
12+
@IsString()
13+
code: string;
14+
15+
@Expose()
16+
@ApiProperty({ description: 'Additional error details', example: ['Field is required'], type: [String] })
17+
@IsOptional()
18+
@IsArray()
19+
@IsString({ each: true })
20+
details?: string[];
21+
22+
constructor(partial: Partial<ErrorDetailsDto>) {
23+
Object.assign(this, partial);
24+
}
25+
}
226

327
/**
428
* Standardized API Response Structure
529
* Unified format for all success and error responses
630
*/
31+
@Exclude()
732
export class ApiResponseDto<T> {
33+
@Expose()
834
@ApiProperty({ description: 'Indicates if the operation was successful' })
35+
@IsBoolean()
936
success: boolean;
1037

38+
@Expose()
1139
@ApiProperty({ description: 'HTTP status code' })
40+
@IsNumber()
1241
statusCode: number;
1342

43+
@Expose()
1444
@ApiProperty({ description: 'Response message' })
45+
@IsString()
1546
message: string;
1647

48+
@Expose()
1749
@ApiProperty({ description: 'Response data payload', required: false })
50+
@IsOptional()
1851
data: T | null;
1952

53+
@Expose()
2054
@ApiProperty({
2155
description: 'Error details if success is false',
2256
required: false,
23-
type: 'object',
24-
properties: {
25-
code: { type: 'string' },
26-
details: { type: 'array', items: { type: 'string' } },
27-
},
57+
type: ErrorDetailsDto,
2858
})
29-
error?: {
30-
code: string;
31-
details?: string[];
32-
};
59+
@IsOptional()
60+
@ValidateNested()
61+
@Type(() => ErrorDetailsDto)
62+
error?: ErrorDetailsDto;
3363

64+
@Expose()
3465
@ApiProperty({ description: 'UTC timestamp of the response' })
66+
@IsString()
67+
@Transform(({ value }) => value || new Date().toISOString())
3568
timestamp: string;
3669

70+
@Expose()
3771
@ApiProperty({ description: 'API request path' })
72+
@IsString()
3873
path: string;
3974

75+
@Expose()
4076
@ApiProperty({ description: 'Unique request/correlation identifier' })
77+
@IsString()
4178
requestId: string;
4279

4380
constructor(partial: Partial<ApiResponseDto<T>>) {
@@ -48,7 +85,13 @@ export class ApiResponseDto<T> {
4885
/**
4986
* Static helper to create a success response
5087
*/
51-
static success<T>(data: T, message = 'Success', statusCode = 200, path?: string, requestId?: string): ApiResponseDto<T> {
88+
static success<T>(
89+
data: T,
90+
message = 'Success',
91+
statusCode = 200,
92+
path?: string,
93+
requestId?: string,
94+
): ApiResponseDto<T> {
5295
return new ApiResponseDto({
5396
success: true,
5497
statusCode,
@@ -75,10 +118,10 @@ export class ApiResponseDto<T> {
75118
statusCode,
76119
message,
77120
data: null,
78-
error: {
121+
error: new ErrorDetailsDto({
79122
code: errorCode,
80123
details,
81-
},
124+
}),
82125
path,
83126
requestId,
84127
});

0 commit comments

Comments
 (0)