|
| 1 | +import { BadRequestException } from '@nestjs/common'; |
1 | 2 | import { ProjectMemberRole } from '@prisma/client'; |
2 | 3 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; |
3 | 4 | import { Transform } from 'class-transformer'; |
4 | | -import { IsEnum, IsNumber, IsOptional } from 'class-validator'; |
| 5 | +import { IsEnum, IsOptional, IsString } from 'class-validator'; |
5 | 6 | import { parseNumericStringId } from 'src/shared/utils/service.utils'; |
6 | 7 |
|
7 | 8 | /** |
8 | | - * Parses optional integer-like input values from query/body payloads. |
| 9 | + * Parses optional user ids from query/body payloads while preserving exact |
| 10 | + * digits. |
9 | 11 | * |
10 | 12 | * @param value Raw unknown value from the incoming payload. |
11 | | - * @returns A truncated number when parseable, otherwise `undefined`. |
| 13 | + * @returns A normalized numeric-string id when parseable, otherwise |
| 14 | + * `undefined`. |
12 | 15 | * @throws {BadRequestException} If a provided value is not a numeric string or |
13 | | - * finite number. |
| 16 | + * finite number within the supported bigint range. |
14 | 17 | */ |
15 | | -function parseOptionalInteger(value: unknown): number | undefined { |
| 18 | +function parseOptionalUserId(value: unknown): string | undefined { |
16 | 19 | if (typeof value === 'undefined' || value === null || value === '') { |
17 | 20 | return undefined; |
18 | 21 | } |
19 | 22 |
|
20 | 23 | if (typeof value === 'number') { |
21 | | - return Number( |
22 | | - parseNumericStringId(String(Math.trunc(value)), 'User id'), |
23 | | - ); |
| 24 | + return parseNumericStringId( |
| 25 | + String(Math.trunc(value)), |
| 26 | + 'User id', |
| 27 | + ).toString(); |
24 | 28 | } |
25 | 29 |
|
26 | | - return Number(parseNumericStringId(String(value), 'User id')); |
| 30 | + if (typeof value === 'string' || typeof value === 'bigint') { |
| 31 | + return parseNumericStringId(String(value), 'User id').toString(); |
| 32 | + } |
| 33 | + |
| 34 | + throw new BadRequestException('User id must be a numeric string.'); |
27 | 35 | } |
28 | 36 |
|
29 | 37 | /** |
30 | 38 | * DTO for creating a project member. |
31 | 39 | * |
32 | | - * Validation requires `role`, while the service still defensively falls back |
33 | | - * to `getDefaultProjectRole` when role is missing in runtime payloads. |
| 40 | + * Validation requires `role`. `userId` is normalized to a numeric string so |
| 41 | + * values beyond JavaScript's safe integer range are preserved exactly. |
34 | 42 | */ |
35 | 43 | export class CreateMemberDto { |
36 | | - @ApiPropertyOptional({ description: 'User id. Defaults to current user.' }) |
| 44 | + @ApiPropertyOptional({ |
| 45 | + description: 'User id. Defaults to current user.', |
| 46 | + type: String, |
| 47 | + }) |
37 | 48 | @IsOptional() |
38 | | - @Transform(({ value }) => parseOptionalInteger(value)) |
39 | | - @IsNumber() |
40 | | - userId?: number; |
| 49 | + @Transform(({ value }) => parseOptionalUserId(value)) |
| 50 | + @IsString() |
| 51 | + userId?: string; |
41 | 52 |
|
42 | 53 | @ApiProperty({ enum: ProjectMemberRole, enumName: 'ProjectMemberRole' }) |
43 | 54 | @IsEnum(ProjectMemberRole) |
|
0 commit comments