1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-26 10:50:29 +02:00

refactor(server): more consistent param validation (#2166)

This commit is contained in:
Michel Heusschen 2023-04-05 00:24:08 +02:00 committed by GitHub
parent ad680b6a35
commit d5f2e3e45c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 109 additions and 36 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -7,7 +7,6 @@ import {
Param, Param,
Delete, Delete,
ValidationPipe, ValidationPipe,
ParseUUIDPipe,
Put, Put,
Query, Query,
Response, Response,
@ -33,8 +32,7 @@ import {
} from '../../constants/download.constant'; } from '../../constants/download.constant';
import { DownloadDto } from '../asset/dto/download-library.dto'; import { DownloadDto } from '../asset/dto/download-library.dto';
import { CreateAlbumShareLinkDto as CreateAlbumSharedLinkDto } from './dto/create-album-shared-link.dto'; import { CreateAlbumShareLinkDto as CreateAlbumSharedLinkDto } from './dto/create-album-shared-link.dto';
import { AlbumIdDto } from './dto/album-id.dto';
// TODO might be worth creating a AlbumParamsDto that validates `albumId` instead of using the pipe.
@ApiTags('Album') @ApiTags('Album')
@Controller('album') @Controller('album')
@ -58,7 +56,7 @@ export class AlbumController {
async addUsersToAlbum( async addUsersToAlbum(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) addUsersDto: AddUsersDto, @Body(ValidationPipe) addUsersDto: AddUsersDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, @Param() { albumId }: AlbumIdDto,
) { ) {
return this.albumService.addUsersToAlbum(authUser, addUsersDto, albumId); return this.albumService.addUsersToAlbum(authUser, addUsersDto, albumId);
} }
@ -68,17 +66,14 @@ export class AlbumController {
async addAssetsToAlbum( async addAssetsToAlbum(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) addAssetsDto: AddAssetsDto, @Body(ValidationPipe) addAssetsDto: AddAssetsDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, @Param() { albumId }: AlbumIdDto,
): Promise<AddAssetsResponseDto> { ): Promise<AddAssetsResponseDto> {
return this.albumService.addAssetsToAlbum(authUser, addAssetsDto, albumId); return this.albumService.addAssetsToAlbum(authUser, addAssetsDto, albumId);
} }
@Authenticated({ isShared: true }) @Authenticated({ isShared: true })
@Get('/:albumId') @Get('/:albumId')
async getAlbumInfo( async getAlbumInfo(@GetAuthUser() authUser: AuthUserDto, @Param() { albumId }: AlbumIdDto) {
@GetAuthUser() authUser: AuthUserDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
) {
return this.albumService.getAlbumInfo(authUser, albumId); return this.albumService.getAlbumInfo(authUser, albumId);
} }
@ -87,17 +82,14 @@ export class AlbumController {
async removeAssetFromAlbum( async removeAssetFromAlbum(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) removeAssetsDto: RemoveAssetsDto, @Body(ValidationPipe) removeAssetsDto: RemoveAssetsDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, @Param() { albumId }: AlbumIdDto,
): Promise<AlbumResponseDto> { ): Promise<AlbumResponseDto> {
return this.albumService.removeAssetsFromAlbum(authUser, removeAssetsDto, albumId); return this.albumService.removeAssetsFromAlbum(authUser, removeAssetsDto, albumId);
} }
@Authenticated() @Authenticated()
@Delete('/:albumId') @Delete('/:albumId')
async deleteAlbum( async deleteAlbum(@GetAuthUser() authUser: AuthUserDto, @Param() { albumId }: AlbumIdDto) {
@GetAuthUser() authUser: AuthUserDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
) {
return this.albumService.deleteAlbum(authUser, albumId); return this.albumService.deleteAlbum(authUser, albumId);
} }
@ -105,7 +97,7 @@ export class AlbumController {
@Delete('/:albumId/user/:userId') @Delete('/:albumId/user/:userId')
async removeUserFromAlbum( async removeUserFromAlbum(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, @Param() { albumId }: AlbumIdDto,
@Param('userId', new ParseMeUUIDPipe({ version: '4' })) userId: string, @Param('userId', new ParseMeUUIDPipe({ version: '4' })) userId: string,
) { ) {
return this.albumService.removeUserFromAlbum(authUser, albumId, userId); return this.albumService.removeUserFromAlbum(authUser, albumId, userId);
@ -116,7 +108,7 @@ export class AlbumController {
async updateAlbumInfo( async updateAlbumInfo(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) updateAlbumInfoDto: UpdateAlbumDto, @Body(ValidationPipe) updateAlbumInfoDto: UpdateAlbumDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, @Param() { albumId }: AlbumIdDto,
) { ) {
return this.albumService.updateAlbumInfo(authUser, updateAlbumInfoDto, albumId); return this.albumService.updateAlbumInfo(authUser, updateAlbumInfoDto, albumId);
} }
@ -126,7 +118,7 @@ export class AlbumController {
@ApiOkResponse({ content: { 'application/zip': { schema: { type: 'string', format: 'binary' } } } }) @ApiOkResponse({ content: { 'application/zip': { schema: { type: 'string', format: 'binary' } } } })
async downloadArchive( async downloadArchive(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, @Param() { albumId }: AlbumIdDto,
@Query(new ValidationPipe({ transform: true })) dto: DownloadDto, @Query(new ValidationPipe({ transform: true })) dto: DownloadDto,
@Response({ passthrough: true }) res: Res, @Response({ passthrough: true }) res: Res,
) { ) {

View File

@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsUUID } from 'class-validator';
export class AlbumIdDto {
@IsNotEmpty()
@IsUUID('4')
@ApiProperty({ format: 'uuid' })
albumId!: string;
}

View File

@ -57,6 +57,8 @@ import { AssetSearchDto } from './dto/asset-search.dto';
import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config'; import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config';
import FileNotEmptyValidator from '../validation/file-not-empty-validator'; import FileNotEmptyValidator from '../validation/file-not-empty-validator';
import { RemoveAssetsDto } from '../album/dto/remove-assets.dto'; import { RemoveAssetsDto } from '../album/dto/remove-assets.dto';
import { AssetIdDto } from './dto/asset-id.dto';
import { DeviceIdDto } from './dto/device-id.dto';
function asStreamableFile({ stream, type, length }: ImmichReadStream) { function asStreamableFile({ stream, type, length }: ImmichReadStream) {
return new StreamableFile(stream, { type, length }); return new StreamableFile(stream, { type, length });
@ -111,7 +113,7 @@ export class AssetController {
async downloadFile( async downloadFile(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Response({ passthrough: true }) res: Res, @Response({ passthrough: true }) res: Res,
@Param('assetId') assetId: string, @Param() { assetId }: AssetIdDto,
) { ) {
return this.assetService.downloadFile(authUser, assetId).then(asStreamableFile); return this.assetService.downloadFile(authUser, assetId).then(asStreamableFile);
} }
@ -163,7 +165,7 @@ export class AssetController {
@Headers() headers: Record<string, string>, @Headers() headers: Record<string, string>,
@Response({ passthrough: true }) res: Res, @Response({ passthrough: true }) res: Res,
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto, @Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
@Param('assetId') assetId: string, @Param() { assetId }: AssetIdDto,
) { ) {
await this.assetService.checkAssetsAccess(authUser, [assetId]); await this.assetService.checkAssetsAccess(authUser, [assetId]);
return this.assetService.serveFile(authUser, assetId, query, res, headers); return this.assetService.serveFile(authUser, assetId, query, res, headers);
@ -177,7 +179,7 @@ export class AssetController {
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Headers() headers: Record<string, string>, @Headers() headers: Record<string, string>,
@Response({ passthrough: true }) res: Res, @Response({ passthrough: true }) res: Res,
@Param('assetId') assetId: string, @Param() { assetId }: AssetIdDto,
@Query(new ValidationPipe({ transform: true })) query: GetAssetThumbnailDto, @Query(new ValidationPipe({ transform: true })) query: GetAssetThumbnailDto,
) { ) {
await this.assetService.checkAssetsAccess(authUser, [assetId]); await this.assetService.checkAssetsAccess(authUser, [assetId]);
@ -258,7 +260,7 @@ export class AssetController {
*/ */
@Authenticated() @Authenticated()
@Get('/:deviceId') @Get('/:deviceId')
async getUserAssetsByDeviceId(@GetAuthUser() authUser: AuthUserDto, @Param('deviceId') deviceId: string) { async getUserAssetsByDeviceId(@GetAuthUser() authUser: AuthUserDto, @Param() { deviceId }: DeviceIdDto) {
return await this.assetService.getUserAssetsByDeviceId(authUser, deviceId); return await this.assetService.getUserAssetsByDeviceId(authUser, deviceId);
} }
@ -269,7 +271,7 @@ export class AssetController {
@Get('/assetById/:assetId') @Get('/assetById/:assetId')
async getAssetById( async getAssetById(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Param('assetId') assetId: string, @Param() { assetId }: AssetIdDto,
): Promise<AssetResponseDto> { ): Promise<AssetResponseDto> {
await this.assetService.checkAssetsAccess(authUser, [assetId]); await this.assetService.checkAssetsAccess(authUser, [assetId]);
return await this.assetService.getAssetById(authUser, assetId); return await this.assetService.getAssetById(authUser, assetId);
@ -282,7 +284,7 @@ export class AssetController {
@Put('/:assetId') @Put('/:assetId')
async updateAsset( async updateAsset(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Param('assetId') assetId: string, @Param() { assetId }: AssetIdDto,
@Body(ValidationPipe) dto: UpdateAssetDto, @Body(ValidationPipe) dto: UpdateAssetDto,
): Promise<AssetResponseDto> { ): Promise<AssetResponseDto> {
await this.assetService.checkAssetsAccess(authUser, [assetId], true); await this.assetService.checkAssetsAccess(authUser, [assetId], true);

View File

@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsUUID } from 'class-validator';
export class AssetIdDto {
@IsNotEmpty()
@IsUUID('4')
@ApiProperty({ format: 'uuid' })
assetId!: string;
}

View File

@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsUUID } from 'class-validator';
export class DeviceIdDto {
@IsNotEmpty()
@IsUUID('4')
@ApiProperty({ format: 'uuid' })
deviceId!: string;
}

View File

@ -6,6 +6,7 @@ import { Authenticated } from '../../decorators/authenticated.decorator';
import { ApiTags } from '@nestjs/swagger'; import { ApiTags } from '@nestjs/swagger';
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator'; import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
import { mapTag, TagResponseDto } from '@app/domain'; import { mapTag, TagResponseDto } from '@app/domain';
import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto';
@Authenticated() @Authenticated()
@ApiTags('Tag') @ApiTags('Tag')
@ -27,7 +28,7 @@ export class TagController {
} }
@Get(':id') @Get(':id')
async findOne(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<TagResponseDto> { async findOne(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<TagResponseDto> {
const tag = await this.tagService.findOne(authUser, id); const tag = await this.tagService.findOne(authUser, id);
return mapTag(tag); return mapTag(tag);
} }
@ -35,14 +36,14 @@ export class TagController {
@Patch(':id') @Patch(':id')
update( update(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Param('id') id: string, @Param() { id }: UUIDParamDto,
@Body(ValidationPipe) updateTagDto: UpdateTagDto, @Body(ValidationPipe) updateTagDto: UpdateTagDto,
): Promise<TagResponseDto> { ): Promise<TagResponseDto> {
return this.tagService.update(authUser, id, updateTagDto); return this.tagService.update(authUser, id, updateTagDto);
} }
@Delete(':id') @Delete(':id')
delete(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<void> { delete(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.tagService.remove(authUser, id); return this.tagService.remove(authUser, id);
} }
} }

View File

@ -11,6 +11,7 @@ import { ApiTags } from '@nestjs/swagger';
import { GetAuthUser } from '../decorators/auth-user.decorator'; import { GetAuthUser } from '../decorators/auth-user.decorator';
import { Authenticated } from '../decorators/authenticated.decorator'; import { Authenticated } from '../decorators/authenticated.decorator';
import { UseValidation } from '../decorators/use-validation.decorator'; import { UseValidation } from '../decorators/use-validation.decorator';
import { UUIDParamDto } from './dto/uuid-param.dto';
@ApiTags('API Key') @ApiTags('API Key')
@Controller('api-key') @Controller('api-key')
@ -30,21 +31,21 @@ export class APIKeyController {
} }
@Get(':id') @Get(':id')
getKey(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<APIKeyResponseDto> { getKey(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> {
return this.service.getById(authUser, id); return this.service.getById(authUser, id);
} }
@Put(':id') @Put(':id')
updateKey( updateKey(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Param('id') id: string, @Param() { id }: UUIDParamDto,
@Body() dto: APIKeyUpdateDto, @Body() dto: APIKeyUpdateDto,
): Promise<APIKeyResponseDto> { ): Promise<APIKeyResponseDto> {
return this.service.update(authUser, id, dto); return this.service.update(authUser, id, dto);
} }
@Delete(':id') @Delete(':id')
deleteKey(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<void> { deleteKey(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(authUser, id); return this.service.delete(authUser, id);
} }
} }

View File

@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsUUID } from 'class-validator';
export class UUIDParamDto {
@IsNotEmpty()
@IsUUID('4')
@ApiProperty({ format: 'uuid' })
id!: string;
}

View File

@ -4,6 +4,7 @@ import { ApiTags } from '@nestjs/swagger';
import { GetAuthUser } from '../decorators/auth-user.decorator'; import { GetAuthUser } from '../decorators/auth-user.decorator';
import { Authenticated } from '../decorators/authenticated.decorator'; import { Authenticated } from '../decorators/authenticated.decorator';
import { UseValidation } from '../decorators/use-validation.decorator'; import { UseValidation } from '../decorators/use-validation.decorator';
import { UUIDParamDto } from './dto/uuid-param.dto';
@ApiTags('share') @ApiTags('share')
@Controller('share') @Controller('share')
@ -25,13 +26,16 @@ export class ShareController {
@Authenticated() @Authenticated()
@Get(':id') @Get(':id')
getSharedLinkById(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<SharedLinkResponseDto> { getSharedLinkById(
@GetAuthUser() authUser: AuthUserDto,
@Param() { id }: UUIDParamDto,
): Promise<SharedLinkResponseDto> {
return this.service.getById(authUser, id, true); return this.service.getById(authUser, id, true);
} }
@Authenticated() @Authenticated()
@Delete(':id') @Delete(':id')
removeSharedLink(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<void> { removeSharedLink(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.remove(authUser, id); return this.service.remove(authUser, id);
} }
@ -39,7 +43,7 @@ export class ShareController {
@Patch(':id') @Patch(':id')
editSharedLink( editSharedLink(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Param('id') id: string, @Param() { id }: UUIDParamDto,
@Body() dto: EditSharedLinkDto, @Body() dto: EditSharedLinkDto,
): Promise<SharedLinkResponseDto> { ): Promise<SharedLinkResponseDto> {
return this.service.edit(authUser, id, dto); return this.service.edit(authUser, id, dto);

View File

@ -28,6 +28,7 @@ import { CreateProfileImageDto } from '@app/domain';
import { CreateProfileImageResponseDto } from '@app/domain'; import { CreateProfileImageResponseDto } from '@app/domain';
import { UserCountDto } from '@app/domain'; import { UserCountDto } from '@app/domain';
import { UseValidation } from '../decorators/use-validation.decorator'; import { UseValidation } from '../decorators/use-validation.decorator';
import { UserIdDto } from '@app/domain/user/dto/user-id.dto';
@ApiTags('User') @ApiTags('User')
@Controller('user') @Controller('user')
@ -43,7 +44,7 @@ export class UserController {
@Authenticated() @Authenticated()
@Get('/info/:userId') @Get('/info/:userId')
getUserById(@Param('userId') userId: string): Promise<UserResponseDto> { getUserById(@Param() { userId }: UserIdDto): Promise<UserResponseDto> {
return this.service.getUserById(userId); return this.service.getUserById(userId);
} }
@ -66,13 +67,13 @@ export class UserController {
@Authenticated({ admin: true }) @Authenticated({ admin: true })
@Delete('/:userId') @Delete('/:userId')
deleteUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise<UserResponseDto> { deleteUser(@GetAuthUser() authUser: AuthUserDto, @Param() { userId }: UserIdDto): Promise<UserResponseDto> {
return this.service.deleteUser(authUser, userId); return this.service.deleteUser(authUser, userId);
} }
@Authenticated({ admin: true }) @Authenticated({ admin: true })
@Post('/:userId/restore') @Post('/:userId/restore')
restoreUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise<UserResponseDto> { restoreUser(@GetAuthUser() authUser: AuthUserDto, @Param() { userId }: UserIdDto): Promise<UserResponseDto> {
return this.service.restoreUser(authUser, userId); return this.service.restoreUser(authUser, userId);
} }
@ -100,7 +101,7 @@ export class UserController {
@Authenticated() @Authenticated()
@Get('/profile-image/:userId') @Get('/profile-image/:userId')
@Header('Cache-Control', 'max-age=600') @Header('Cache-Control', 'max-age=600')
async getProfileImage(@Param('userId') userId: string, @Response({ passthrough: true }) res: Res): Promise<any> { async getProfileImage(@Param() { userId }: UserIdDto, @Response({ passthrough: true }) res: Res): Promise<any> {
const readableStream = await this.service.getUserProfileImage(userId); const readableStream = await this.service.getUserProfileImage(userId);
res.header('Content-Type', 'image/jpeg'); res.header('Content-Type', 'image/jpeg');
return new StreamableFile(readableStream); return new StreamableFile(readableStream);

View File

@ -172,6 +172,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -209,6 +210,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -256,6 +258,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -1117,6 +1120,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -1154,6 +1158,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -1184,6 +1189,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -1479,6 +1485,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -1580,6 +1587,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -1619,6 +1627,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -1699,6 +1708,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -1788,6 +1798,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
}, },
@ -1955,6 +1966,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
}, },
@ -2003,6 +2015,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
}, },
@ -2414,6 +2427,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -2456,6 +2470,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
}, },
@ -2503,6 +2518,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -2850,6 +2866,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -2887,6 +2904,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -2934,6 +2952,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -2996,6 +3015,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -3045,6 +3065,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
}, },
@ -3100,6 +3121,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -3149,6 +3171,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
}, },
@ -3194,6 +3217,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -3224,6 +3248,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@ -3273,6 +3298,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
}, },
@ -3313,6 +3339,7 @@
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
}, },

View File

@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsUUID } from 'class-validator';
export class UserIdDto {
@IsNotEmpty()
@IsUUID('4')
@ApiProperty({ format: 'uuid' })
userId!: string;
}