From d5f2e3e45cdc96f504f6328ac51ace381d0437f6 Mon Sep 17 00:00:00 2001 From: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com> Date: Wed, 5 Apr 2023 00:24:08 +0200 Subject: [PATCH] refactor(server): more consistent param validation (#2166) --- mobile/openapi/doc/APIKeyApi.md | Bin 8454 -> 8532 bytes mobile/openapi/doc/AlbumApi.md | Bin 21941 -> 22109 bytes mobile/openapi/doc/AssetApi.md | Bin 43423 -> 43548 bytes mobile/openapi/doc/ShareApi.md | Bin 8624 -> 8702 bytes mobile/openapi/doc/TagApi.md | Bin 8238 -> 8316 bytes mobile/openapi/doc/UserApi.md | Bin 16163 -> 16251 bytes .../src/api-v1/album/album.controller.ts | 26 ++++++----------- .../src/api-v1/album/dto/album-id.dto.ts | 9 ++++++ .../src/api-v1/asset/asset.controller.ts | 14 +++++---- .../src/api-v1/asset/dto/asset-id.dto.ts | 9 ++++++ .../src/api-v1/asset/dto/device-id.dto.ts | 9 ++++++ .../immich/src/api-v1/tag/tag.controller.ts | 7 +++-- .../src/controllers/api-key.controller.ts | 7 +++-- .../src/controllers/dto/uuid-param.dto.ts | 9 ++++++ .../src/controllers/share.controller.ts | 10 +++++-- .../immich/src/controllers/user.controller.ts | 9 +++--- server/immich-openapi-specs.json | 27 ++++++++++++++++++ .../libs/domain/src/user/dto/user-id.dto.ts | 9 ++++++ 18 files changed, 109 insertions(+), 36 deletions(-) create mode 100644 server/apps/immich/src/api-v1/album/dto/album-id.dto.ts create mode 100644 server/apps/immich/src/api-v1/asset/dto/asset-id.dto.ts create mode 100644 server/apps/immich/src/api-v1/asset/dto/device-id.dto.ts create mode 100644 server/apps/immich/src/controllers/dto/uuid-param.dto.ts create mode 100644 server/libs/domain/src/user/dto/user-id.dto.ts diff --git a/mobile/openapi/doc/APIKeyApi.md b/mobile/openapi/doc/APIKeyApi.md index ecf2fcc674ba15804de10f212f94782f9db8332f..4bb570bfe122e73c5146a6df4d79e6a202f52087 100644 GIT binary patch delta 141 zcmZp3y5h9qGPjDcg^2+O=vpMF8R!}sCZ*^m85yVQ8X6>7nx&egrWqKdY`)8Vg_S6k Ylkaj%O+G2EwE4SW50Pf~%2qN10CSZo2mk;8 delta 59 wcmccO)aJC|GB;OdN_=WXVs1fB>g4x=Qkw;NcCjLfZGJC!0$I)G+p?9+0M1(%yZ`_I diff --git a/mobile/openapi/doc/AlbumApi.md b/mobile/openapi/doc/AlbumApi.md index c51909bace33b186bfd5514cc5770fc20ba652b7..72cb3392ed080fae7e6d9f9387f868e6ac5d9fe6 100644 GIT binary patch delta 109 zcmdnGn(^)$#tmK^lkIJdCeJrx-<-!G#t!GK;O7?sbKZGNZhotLhZQdKR&y#3h_iXV YnHV2j^rp)hHaO>`cLajt8dSgx0LjQGh5!Hn delta 175 zcmcb+hH>j^#tmK^{E0b9rMaFd@u?Mwxdl0?oAWrd+0g|j=W$4EzRJ%d!U|SBIn&2* ovyRFgR&;fnb+lITUMBdC=b0ENmzPXGV_ diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index e1a4165b3b8e32654c7f7d9cdac788ade48f4af0..53208634b3f1d7e92d7e3d7eab0e3bc81bd235a8 100644 GIT binary patch delta 285 zcmbP#nQ6`yrVTsvRg5i63_w8FB00@K*U&I2MK{UFI91osAj#4!)g(2|z#wJvghis8 z@93YACSLU!?-B*#R3BI*wmCJ2OPDy-3(d?nZ>`@+q-v16CrwsjC(dk;>Kn6;^8o;l CY*mK< delta 89 zcmbPpg=zj}rVTsv851Y}U!*+wRF3=PH)cGWT?{Iv;X?kKU42v)Al%K1bGU^k>n@g` Zyv|mAGe^TtVW{}8$x7@{X6WqWd;s|HCpQ29 diff --git a/mobile/openapi/doc/ShareApi.md b/mobile/openapi/doc/ShareApi.md index a0673dd28def861ab9e4af53d00379355dad807e..70ceafff32d60823c33755ddc9e75bd97ed175fe 100644 GIT binary patch delta 133 zcmdns{LgvAdR7%<3ljqn(6vZTGtf0OOiIyBGBQrpH8e=FG)px}O*1e^*}RjLPnbBB P7bVo#h*QZZzn%#IyjUbj delta 59 xcmez8yuo?HdRDH?l=#$&#N2|M)XkS!`Gk@9ll|qTHp@%$upx7nx&egrWqKdY>wtmBu?ez TXl|*^55(TH5@+sBnNVf`K)NPA delta 59 wcmez4u+CvaAU9WLN_=WXVs1fB>f~QyQk(m^-I2vM{}SV5LsGL@T{e^%0J<9%8UO$Q diff --git a/mobile/openapi/doc/UserApi.md b/mobile/openapi/doc/UserApi.md index 1982ea510604547d4e4e28fe25d5abcd3d6f9a18..0bd35a904cb23f19b7b7d642c1ed9066cb2bf95a 100644 GIT binary patch delta 187 zcmZ2n_q%Sx9bpw?3ljqn(6vZTGtf0OOiIyBGBQrpH8e=FG)px}O*1e^nS9aIY_o#M bK0)GDE2%wVC0@0MPBkY{syAOW4dMghiI{EnQ+vdUMYv0+niGp~yqOFq E0AboC6#xJL diff --git a/server/apps/immich/src/api-v1/album/album.controller.ts b/server/apps/immich/src/api-v1/album/album.controller.ts index cf94e9e6ac..d204f44afd 100644 --- a/server/apps/immich/src/api-v1/album/album.controller.ts +++ b/server/apps/immich/src/api-v1/album/album.controller.ts @@ -7,7 +7,6 @@ import { Param, Delete, ValidationPipe, - ParseUUIDPipe, Put, Query, Response, @@ -33,8 +32,7 @@ import { } from '../../constants/download.constant'; import { DownloadDto } from '../asset/dto/download-library.dto'; import { CreateAlbumShareLinkDto as CreateAlbumSharedLinkDto } from './dto/create-album-shared-link.dto'; - -// TODO might be worth creating a AlbumParamsDto that validates `albumId` instead of using the pipe. +import { AlbumIdDto } from './dto/album-id.dto'; @ApiTags('Album') @Controller('album') @@ -58,7 +56,7 @@ export class AlbumController { async addUsersToAlbum( @GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) addUsersDto: AddUsersDto, - @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, + @Param() { albumId }: AlbumIdDto, ) { return this.albumService.addUsersToAlbum(authUser, addUsersDto, albumId); } @@ -68,17 +66,14 @@ export class AlbumController { async addAssetsToAlbum( @GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) addAssetsDto: AddAssetsDto, - @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, + @Param() { albumId }: AlbumIdDto, ): Promise { return this.albumService.addAssetsToAlbum(authUser, addAssetsDto, albumId); } @Authenticated({ isShared: true }) @Get('/:albumId') - async getAlbumInfo( - @GetAuthUser() authUser: AuthUserDto, - @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, - ) { + async getAlbumInfo(@GetAuthUser() authUser: AuthUserDto, @Param() { albumId }: AlbumIdDto) { return this.albumService.getAlbumInfo(authUser, albumId); } @@ -87,17 +82,14 @@ export class AlbumController { async removeAssetFromAlbum( @GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) removeAssetsDto: RemoveAssetsDto, - @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, + @Param() { albumId }: AlbumIdDto, ): Promise { return this.albumService.removeAssetsFromAlbum(authUser, removeAssetsDto, albumId); } @Authenticated() @Delete('/:albumId') - async deleteAlbum( - @GetAuthUser() authUser: AuthUserDto, - @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, - ) { + async deleteAlbum(@GetAuthUser() authUser: AuthUserDto, @Param() { albumId }: AlbumIdDto) { return this.albumService.deleteAlbum(authUser, albumId); } @@ -105,7 +97,7 @@ export class AlbumController { @Delete('/:albumId/user/:userId') async removeUserFromAlbum( @GetAuthUser() authUser: AuthUserDto, - @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, + @Param() { albumId }: AlbumIdDto, @Param('userId', new ParseMeUUIDPipe({ version: '4' })) userId: string, ) { return this.albumService.removeUserFromAlbum(authUser, albumId, userId); @@ -116,7 +108,7 @@ export class AlbumController { async updateAlbumInfo( @GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) updateAlbumInfoDto: UpdateAlbumDto, - @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, + @Param() { albumId }: AlbumIdDto, ) { return this.albumService.updateAlbumInfo(authUser, updateAlbumInfoDto, albumId); } @@ -126,7 +118,7 @@ export class AlbumController { @ApiOkResponse({ content: { 'application/zip': { schema: { type: 'string', format: 'binary' } } } }) async downloadArchive( @GetAuthUser() authUser: AuthUserDto, - @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, + @Param() { albumId }: AlbumIdDto, @Query(new ValidationPipe({ transform: true })) dto: DownloadDto, @Response({ passthrough: true }) res: Res, ) { diff --git a/server/apps/immich/src/api-v1/album/dto/album-id.dto.ts b/server/apps/immich/src/api-v1/album/dto/album-id.dto.ts new file mode 100644 index 0000000000..c9bf09e328 --- /dev/null +++ b/server/apps/immich/src/api-v1/album/dto/album-id.dto.ts @@ -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; +} diff --git a/server/apps/immich/src/api-v1/asset/asset.controller.ts b/server/apps/immich/src/api-v1/asset/asset.controller.ts index d63e99f83c..0c14386157 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -57,6 +57,8 @@ import { AssetSearchDto } from './dto/asset-search.dto'; import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config'; import FileNotEmptyValidator from '../validation/file-not-empty-validator'; 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) { return new StreamableFile(stream, { type, length }); @@ -111,7 +113,7 @@ export class AssetController { async downloadFile( @GetAuthUser() authUser: AuthUserDto, @Response({ passthrough: true }) res: Res, - @Param('assetId') assetId: string, + @Param() { assetId }: AssetIdDto, ) { return this.assetService.downloadFile(authUser, assetId).then(asStreamableFile); } @@ -163,7 +165,7 @@ export class AssetController { @Headers() headers: Record, @Response({ passthrough: true }) res: Res, @Query(new ValidationPipe({ transform: true })) query: ServeFileDto, - @Param('assetId') assetId: string, + @Param() { assetId }: AssetIdDto, ) { await this.assetService.checkAssetsAccess(authUser, [assetId]); return this.assetService.serveFile(authUser, assetId, query, res, headers); @@ -177,7 +179,7 @@ export class AssetController { @GetAuthUser() authUser: AuthUserDto, @Headers() headers: Record, @Response({ passthrough: true }) res: Res, - @Param('assetId') assetId: string, + @Param() { assetId }: AssetIdDto, @Query(new ValidationPipe({ transform: true })) query: GetAssetThumbnailDto, ) { await this.assetService.checkAssetsAccess(authUser, [assetId]); @@ -258,7 +260,7 @@ export class AssetController { */ @Authenticated() @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); } @@ -269,7 +271,7 @@ export class AssetController { @Get('/assetById/:assetId') async getAssetById( @GetAuthUser() authUser: AuthUserDto, - @Param('assetId') assetId: string, + @Param() { assetId }: AssetIdDto, ): Promise { await this.assetService.checkAssetsAccess(authUser, [assetId]); return await this.assetService.getAssetById(authUser, assetId); @@ -282,7 +284,7 @@ export class AssetController { @Put('/:assetId') async updateAsset( @GetAuthUser() authUser: AuthUserDto, - @Param('assetId') assetId: string, + @Param() { assetId }: AssetIdDto, @Body(ValidationPipe) dto: UpdateAssetDto, ): Promise { await this.assetService.checkAssetsAccess(authUser, [assetId], true); diff --git a/server/apps/immich/src/api-v1/asset/dto/asset-id.dto.ts b/server/apps/immich/src/api-v1/asset/dto/asset-id.dto.ts new file mode 100644 index 0000000000..5404878382 --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/dto/asset-id.dto.ts @@ -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; +} diff --git a/server/apps/immich/src/api-v1/asset/dto/device-id.dto.ts b/server/apps/immich/src/api-v1/asset/dto/device-id.dto.ts new file mode 100644 index 0000000000..ff2f4163b5 --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/dto/device-id.dto.ts @@ -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; +} diff --git a/server/apps/immich/src/api-v1/tag/tag.controller.ts b/server/apps/immich/src/api-v1/tag/tag.controller.ts index b02b222276..d769b157aa 100644 --- a/server/apps/immich/src/api-v1/tag/tag.controller.ts +++ b/server/apps/immich/src/api-v1/tag/tag.controller.ts @@ -6,6 +6,7 @@ import { Authenticated } from '../../decorators/authenticated.decorator'; import { ApiTags } from '@nestjs/swagger'; import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator'; import { mapTag, TagResponseDto } from '@app/domain'; +import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto'; @Authenticated() @ApiTags('Tag') @@ -27,7 +28,7 @@ export class TagController { } @Get(':id') - async findOne(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise { + async findOne(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { const tag = await this.tagService.findOne(authUser, id); return mapTag(tag); } @@ -35,14 +36,14 @@ export class TagController { @Patch(':id') update( @GetAuthUser() authUser: AuthUserDto, - @Param('id') id: string, + @Param() { id }: UUIDParamDto, @Body(ValidationPipe) updateTagDto: UpdateTagDto, ): Promise { return this.tagService.update(authUser, id, updateTagDto); } @Delete(':id') - delete(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise { + delete(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.tagService.remove(authUser, id); } } diff --git a/server/apps/immich/src/controllers/api-key.controller.ts b/server/apps/immich/src/controllers/api-key.controller.ts index d666bd4272..e025b248f6 100644 --- a/server/apps/immich/src/controllers/api-key.controller.ts +++ b/server/apps/immich/src/controllers/api-key.controller.ts @@ -11,6 +11,7 @@ import { ApiTags } from '@nestjs/swagger'; import { GetAuthUser } from '../decorators/auth-user.decorator'; import { Authenticated } from '../decorators/authenticated.decorator'; import { UseValidation } from '../decorators/use-validation.decorator'; +import { UUIDParamDto } from './dto/uuid-param.dto'; @ApiTags('API Key') @Controller('api-key') @@ -30,21 +31,21 @@ export class APIKeyController { } @Get(':id') - getKey(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise { + getKey(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.getById(authUser, id); } @Put(':id') updateKey( @GetAuthUser() authUser: AuthUserDto, - @Param('id') id: string, + @Param() { id }: UUIDParamDto, @Body() dto: APIKeyUpdateDto, ): Promise { return this.service.update(authUser, id, dto); } @Delete(':id') - deleteKey(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise { + deleteKey(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.delete(authUser, id); } } diff --git a/server/apps/immich/src/controllers/dto/uuid-param.dto.ts b/server/apps/immich/src/controllers/dto/uuid-param.dto.ts new file mode 100644 index 0000000000..6e1b5a36cc --- /dev/null +++ b/server/apps/immich/src/controllers/dto/uuid-param.dto.ts @@ -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; +} diff --git a/server/apps/immich/src/controllers/share.controller.ts b/server/apps/immich/src/controllers/share.controller.ts index 72b0926d2b..aef3dad793 100644 --- a/server/apps/immich/src/controllers/share.controller.ts +++ b/server/apps/immich/src/controllers/share.controller.ts @@ -4,6 +4,7 @@ import { ApiTags } from '@nestjs/swagger'; import { GetAuthUser } from '../decorators/auth-user.decorator'; import { Authenticated } from '../decorators/authenticated.decorator'; import { UseValidation } from '../decorators/use-validation.decorator'; +import { UUIDParamDto } from './dto/uuid-param.dto'; @ApiTags('share') @Controller('share') @@ -25,13 +26,16 @@ export class ShareController { @Authenticated() @Get(':id') - getSharedLinkById(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise { + getSharedLinkById( + @GetAuthUser() authUser: AuthUserDto, + @Param() { id }: UUIDParamDto, + ): Promise { return this.service.getById(authUser, id, true); } @Authenticated() @Delete(':id') - removeSharedLink(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise { + removeSharedLink(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.remove(authUser, id); } @@ -39,7 +43,7 @@ export class ShareController { @Patch(':id') editSharedLink( @GetAuthUser() authUser: AuthUserDto, - @Param('id') id: string, + @Param() { id }: UUIDParamDto, @Body() dto: EditSharedLinkDto, ): Promise { return this.service.edit(authUser, id, dto); diff --git a/server/apps/immich/src/controllers/user.controller.ts b/server/apps/immich/src/controllers/user.controller.ts index 9e7bd311fe..c1da0427aa 100644 --- a/server/apps/immich/src/controllers/user.controller.ts +++ b/server/apps/immich/src/controllers/user.controller.ts @@ -28,6 +28,7 @@ import { CreateProfileImageDto } from '@app/domain'; import { CreateProfileImageResponseDto } from '@app/domain'; import { UserCountDto } from '@app/domain'; import { UseValidation } from '../decorators/use-validation.decorator'; +import { UserIdDto } from '@app/domain/user/dto/user-id.dto'; @ApiTags('User') @Controller('user') @@ -43,7 +44,7 @@ export class UserController { @Authenticated() @Get('/info/:userId') - getUserById(@Param('userId') userId: string): Promise { + getUserById(@Param() { userId }: UserIdDto): Promise { return this.service.getUserById(userId); } @@ -66,13 +67,13 @@ export class UserController { @Authenticated({ admin: true }) @Delete('/:userId') - deleteUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise { + deleteUser(@GetAuthUser() authUser: AuthUserDto, @Param() { userId }: UserIdDto): Promise { return this.service.deleteUser(authUser, userId); } @Authenticated({ admin: true }) @Post('/:userId/restore') - restoreUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise { + restoreUser(@GetAuthUser() authUser: AuthUserDto, @Param() { userId }: UserIdDto): Promise { return this.service.restoreUser(authUser, userId); } @@ -100,7 +101,7 @@ export class UserController { @Authenticated() @Get('/profile-image/:userId') @Header('Cache-Control', 'max-age=600') - async getProfileImage(@Param('userId') userId: string, @Response({ passthrough: true }) res: Res): Promise { + async getProfileImage(@Param() { userId }: UserIdDto, @Response({ passthrough: true }) res: Res): Promise { const readableStream = await this.service.getUserProfileImage(userId); res.header('Content-Type', 'image/jpeg'); return new StreamableFile(readableStream); diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 3eb8159577..147a7f733f 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -172,6 +172,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -209,6 +210,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -256,6 +258,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -1117,6 +1120,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -1154,6 +1158,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -1184,6 +1189,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -1479,6 +1485,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -1580,6 +1587,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -1619,6 +1627,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -1699,6 +1708,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -1788,6 +1798,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } }, @@ -1955,6 +1966,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } }, @@ -2003,6 +2015,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } }, @@ -2414,6 +2427,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -2456,6 +2470,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } }, @@ -2503,6 +2518,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -2850,6 +2866,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -2887,6 +2904,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -2934,6 +2952,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -2996,6 +3015,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -3045,6 +3065,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } }, @@ -3100,6 +3121,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -3149,6 +3171,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } }, @@ -3194,6 +3217,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -3224,6 +3248,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } } @@ -3273,6 +3298,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } }, @@ -3313,6 +3339,7 @@ "required": true, "in": "path", "schema": { + "format": "uuid", "type": "string" } }, diff --git a/server/libs/domain/src/user/dto/user-id.dto.ts b/server/libs/domain/src/user/dto/user-id.dto.ts new file mode 100644 index 0000000000..ce23581e37 --- /dev/null +++ b/server/libs/domain/src/user/dto/user-id.dto.ts @@ -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; +}