1
0
forked from Cutlery/immich

feat(server): improve validation of albums (#2188)

* feat(server): improve validation of albums

* regenerate openapi + fix downloadArchive for web
This commit is contained in:
Michel Heusschen 2023-04-06 19:50:55 +02:00 committed by GitHub
parent b03ce897c7
commit 8e3a7caebd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 164 additions and 83 deletions

View File

@ -294,7 +294,7 @@ void (empty response body)
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **downloadArchive** # **downloadArchive**
> MultipartFile downloadArchive(albumId, skip, key) > MultipartFile downloadArchive(albumId, name, skip, key)
@ -316,11 +316,12 @@ import 'package:openapi/api.dart';
final api_instance = AlbumApi(); final api_instance = AlbumApi();
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final name = name_example; // String |
final skip = 8.14; // num | final skip = 8.14; // num |
final key = key_example; // String | final key = key_example; // String |
try { try {
final result = api_instance.downloadArchive(albumId, skip, key); final result = api_instance.downloadArchive(albumId, name, skip, key);
print(result); print(result);
} catch (e) { } catch (e) {
print('Exception when calling AlbumApi->downloadArchive: $e\n'); print('Exception when calling AlbumApi->downloadArchive: $e\n');
@ -332,6 +333,7 @@ try {
Name | Type | Description | Notes Name | Type | Description | Notes
------------- | ------------- | ------------- | ------------- ------------- | ------------- | ------------- | -------------
**albumId** | **String**| | **albumId** | **String**| |
**name** | **String**| | [optional]
**skip** | **num**| | [optional] **skip** | **num**| | [optional]
**key** | **String**| | [optional] **key** | **String**| | [optional]

View File

@ -414,7 +414,7 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **downloadLibrary** # **downloadLibrary**
> MultipartFile downloadLibrary(skip, key) > MultipartFile downloadLibrary(name, skip, key)
@ -435,11 +435,12 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer'; //defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi(); final api_instance = AssetApi();
final name = name_example; // String |
final skip = 8.14; // num | final skip = 8.14; // num |
final key = key_example; // String | final key = key_example; // String |
try { try {
final result = api_instance.downloadLibrary(skip, key); final result = api_instance.downloadLibrary(name, skip, key);
print(result); print(result);
} catch (e) { } catch (e) {
print('Exception when calling AssetApi->downloadLibrary: $e\n'); print('Exception when calling AssetApi->downloadLibrary: $e\n');
@ -450,6 +451,7 @@ try {
Name | Type | Description | Notes Name | Type | Description | Notes
------------- | ------------- | ------------- | ------------- ------------- | ------------- | ------------- | -------------
**name** | **String**| | [optional]
**skip** | **num**| | [optional] **skip** | **num**| | [optional]
**key** | **String**| | [optional] **key** | **String**| | [optional]

View File

@ -9,7 +9,7 @@ import 'package:openapi/api.dart';
Name | Type | Description | Notes Name | Type | Description | Notes
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
**albumId** | **String** | | **albumId** | **String** | |
**expiresAt** | **String** | | [optional] **expiresAt** | [**DateTime**](DateTime.md) | | [optional]
**allowUpload** | **bool** | | [optional] **allowUpload** | **bool** | | [optional]
**allowDownload** | **bool** | | [optional] **allowDownload** | **bool** | | [optional]
**showExif** | **bool** | | [optional] **showExif** | **bool** | | [optional]

View File

@ -295,10 +295,12 @@ class AlbumApi {
/// ///
/// * [String] albumId (required): /// * [String] albumId (required):
/// ///
/// * [String] name:
///
/// * [num] skip: /// * [num] skip:
/// ///
/// * [String] key: /// * [String] key:
Future<Response> downloadArchiveWithHttpInfo(String albumId, { num? skip, String? key, }) async { Future<Response> downloadArchiveWithHttpInfo(String albumId, { String? name, num? skip, String? key, }) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final path = r'/album/{albumId}/download' final path = r'/album/{albumId}/download'
.replaceAll('{albumId}', albumId); .replaceAll('{albumId}', albumId);
@ -310,6 +312,9 @@ class AlbumApi {
final headerParams = <String, String>{}; final headerParams = <String, String>{};
final formParams = <String, String>{}; final formParams = <String, String>{};
if (name != null) {
queryParams.addAll(_queryParams('', 'name', name));
}
if (skip != null) { if (skip != null) {
queryParams.addAll(_queryParams('', 'skip', skip)); queryParams.addAll(_queryParams('', 'skip', skip));
} }
@ -337,11 +342,13 @@ class AlbumApi {
/// ///
/// * [String] albumId (required): /// * [String] albumId (required):
/// ///
/// * [String] name:
///
/// * [num] skip: /// * [num] skip:
/// ///
/// * [String] key: /// * [String] key:
Future<MultipartFile?> downloadArchive(String albumId, { num? skip, String? key, }) async { Future<MultipartFile?> downloadArchive(String albumId, { String? name, num? skip, String? key, }) async {
final response = await downloadArchiveWithHttpInfo(albumId, skip: skip, key: key, ); final response = await downloadArchiveWithHttpInfo(albumId, name: name, skip: skip, key: key, );
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
} }

View File

@ -422,10 +422,12 @@ class AssetApi {
/// ///
/// Parameters: /// Parameters:
/// ///
/// * [String] name:
///
/// * [num] skip: /// * [num] skip:
/// ///
/// * [String] key: /// * [String] key:
Future<Response> downloadLibraryWithHttpInfo({ num? skip, String? key, }) async { Future<Response> downloadLibraryWithHttpInfo({ String? name, num? skip, String? key, }) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final path = r'/asset/download-library'; final path = r'/asset/download-library';
@ -436,6 +438,9 @@ class AssetApi {
final headerParams = <String, String>{}; final headerParams = <String, String>{};
final formParams = <String, String>{}; final formParams = <String, String>{};
if (name != null) {
queryParams.addAll(_queryParams('', 'name', name));
}
if (skip != null) { if (skip != null) {
queryParams.addAll(_queryParams('', 'skip', skip)); queryParams.addAll(_queryParams('', 'skip', skip));
} }
@ -461,11 +466,13 @@ class AssetApi {
/// ///
/// Parameters: /// Parameters:
/// ///
/// * [String] name:
///
/// * [num] skip: /// * [num] skip:
/// ///
/// * [String] key: /// * [String] key:
Future<MultipartFile?> downloadLibrary({ num? skip, String? key, }) async { Future<MultipartFile?> downloadLibrary({ String? name, num? skip, String? key, }) async {
final response = await downloadLibraryWithHttpInfo( skip: skip, key: key, ); final response = await downloadLibraryWithHttpInfo( name: name, skip: skip, key: key, );
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
} }

View File

@ -29,7 +29,7 @@ class CreateAlbumShareLinkDto {
/// source code must fall back to having a nullable type. /// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note. /// Consider adding a "default:" property in the specification file to hide this note.
/// ///
String? expiresAt; DateTime? expiresAt;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
@ -89,7 +89,7 @@ class CreateAlbumShareLinkDto {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json[r'albumId'] = this.albumId; json[r'albumId'] = this.albumId;
if (this.expiresAt != null) { if (this.expiresAt != null) {
json[r'expiresAt'] = this.expiresAt; json[r'expiresAt'] = this.expiresAt!.toUtc().toIso8601String();
} else { } else {
// json[r'expiresAt'] = null; // json[r'expiresAt'] = null;
} }
@ -136,7 +136,7 @@ class CreateAlbumShareLinkDto {
return CreateAlbumShareLinkDto( return CreateAlbumShareLinkDto(
albumId: mapValueOfType<String>(json, r'albumId')!, albumId: mapValueOfType<String>(json, r'albumId')!,
expiresAt: mapValueOfType<String>(json, r'expiresAt'), expiresAt: mapDateTime(json, r'expiresAt', ''),
allowUpload: mapValueOfType<bool>(json, r'allowUpload'), allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
allowDownload: mapValueOfType<bool>(json, r'allowDownload'), allowDownload: mapValueOfType<bool>(json, r'allowDownload'),
showExif: mapValueOfType<bool>(json, r'showExif'), showExif: mapValueOfType<bool>(json, r'showExif'),

View File

@ -54,7 +54,7 @@ void main() {
// //
// //
//Future<MultipartFile> downloadArchive(String albumId, { num skip, String key }) async //Future<MultipartFile> downloadArchive(String albumId, { String name, num skip, String key }) async
test('test downloadArchive', () async { test('test downloadArchive', () async {
// TODO // TODO
}); });

View File

@ -68,7 +68,7 @@ void main() {
// Current this is not used in any UI element // Current this is not used in any UI element
// //
//Future<MultipartFile> downloadLibrary({ num skip, String key }) async //Future<MultipartFile> downloadLibrary({ String name, num skip, String key }) async
test('test downloadLibrary', () async { test('test downloadLibrary', () async {
// TODO // TODO
}); });

View File

@ -21,7 +21,7 @@ void main() {
// TODO // TODO
}); });
// String expiresAt // DateTime expiresAt
test('to test the property `expiresAt`', () async { test('to test the property `expiresAt`', () async {
// TODO // TODO
}); });

View File

@ -1,16 +1,4 @@
import { import { Controller, Get, Post, Body, Patch, Param, Delete, Put, Query, Response } from '@nestjs/common';
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
ValidationPipe,
Put,
Query,
Response,
} from '@nestjs/common';
import { ParseMeUUIDPipe } from '../validation/parse-me-uuid-pipe'; import { ParseMeUUIDPipe } from '../validation/parse-me-uuid-pipe';
import { AlbumService } from './album.service'; import { AlbumService } from './album.service';
import { CreateAlbumDto } from './dto/create-album.dto'; import { CreateAlbumDto } from './dto/create-album.dto';
@ -33,9 +21,11 @@ import {
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'; import { AlbumIdDto } from './dto/album-id.dto';
import { UseValidation } from '../../decorators/use-validation.decorator';
@ApiTags('Album') @ApiTags('Album')
@Controller('album') @Controller('album')
@UseValidation()
export class AlbumController { export class AlbumController {
constructor(private readonly albumService: AlbumService) {} constructor(private readonly albumService: AlbumService) {}
@ -47,7 +37,8 @@ export class AlbumController {
@Authenticated() @Authenticated()
@Post() @Post()
async createAlbum(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) createAlbumDto: CreateAlbumDto) { async createAlbum(@GetAuthUser() authUser: AuthUserDto, @Body() createAlbumDto: CreateAlbumDto) {
// TODO: Handle nonexistent sharedWithUserIds and assetIds.
return this.albumService.create(authUser, createAlbumDto); return this.albumService.create(authUser, createAlbumDto);
} }
@ -55,9 +46,10 @@ export class AlbumController {
@Put('/:albumId/users') @Put('/:albumId/users')
async addUsersToAlbum( async addUsersToAlbum(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) addUsersDto: AddUsersDto, @Body() addUsersDto: AddUsersDto,
@Param() { albumId }: AlbumIdDto, @Param() { albumId }: AlbumIdDto,
) { ) {
// TODO: Handle nonexistent sharedUserIds.
return this.albumService.addUsersToAlbum(authUser, addUsersDto, albumId); return this.albumService.addUsersToAlbum(authUser, addUsersDto, albumId);
} }
@ -65,9 +57,11 @@ export class AlbumController {
@Put('/:albumId/assets') @Put('/:albumId/assets')
async addAssetsToAlbum( async addAssetsToAlbum(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) addAssetsDto: AddAssetsDto, @Body() addAssetsDto: AddAssetsDto,
@Param() { albumId }: AlbumIdDto, @Param() { albumId }: AlbumIdDto,
): Promise<AddAssetsResponseDto> { ): Promise<AddAssetsResponseDto> {
// TODO: Handle nonexistent assetIds.
// TODO: Disallow adding assets of another user to an album.
return this.albumService.addAssetsToAlbum(authUser, addAssetsDto, albumId); return this.albumService.addAssetsToAlbum(authUser, addAssetsDto, albumId);
} }
@ -81,7 +75,7 @@ export class AlbumController {
@Delete('/:albumId/assets') @Delete('/:albumId/assets')
async removeAssetFromAlbum( async removeAssetFromAlbum(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) removeAssetsDto: RemoveAssetsDto, @Body() removeAssetsDto: RemoveAssetsDto,
@Param() { albumId }: AlbumIdDto, @Param() { albumId }: AlbumIdDto,
): Promise<AlbumResponseDto> { ): Promise<AlbumResponseDto> {
return this.albumService.removeAssetsFromAlbum(authUser, removeAssetsDto, albumId); return this.albumService.removeAssetsFromAlbum(authUser, removeAssetsDto, albumId);
@ -107,9 +101,11 @@ export class AlbumController {
@Patch('/:albumId') @Patch('/:albumId')
async updateAlbumInfo( async updateAlbumInfo(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) updateAlbumInfoDto: UpdateAlbumDto, @Body() updateAlbumInfoDto: UpdateAlbumDto,
@Param() { albumId }: AlbumIdDto, @Param() { albumId }: AlbumIdDto,
) { ) {
// TODO: Handle nonexistent albumThumbnailAssetId.
// TODO: Disallow setting asset from other user as albumThumbnailAssetId.
return this.albumService.updateAlbumInfo(authUser, updateAlbumInfoDto, albumId); return this.albumService.updateAlbumInfo(authUser, updateAlbumInfoDto, albumId);
} }
@ -119,7 +115,7 @@ export class AlbumController {
async downloadArchive( async downloadArchive(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Param() { albumId }: AlbumIdDto, @Param() { albumId }: AlbumIdDto,
@Query(new ValidationPipe({ transform: true })) dto: DownloadDto, @Query() dto: DownloadDto,
@Response({ passthrough: true }) res: Res, @Response({ passthrough: true }) res: Res,
) { ) {
this.albumService.checkDownloadAccess(authUser); this.albumService.checkDownloadAccess(authUser);
@ -140,7 +136,7 @@ export class AlbumController {
@Post('/create-shared-link') @Post('/create-shared-link')
async createAlbumSharedLink( async createAlbumSharedLink(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) createAlbumShareLinkDto: CreateAlbumSharedLinkDto, @Body() createAlbumShareLinkDto: CreateAlbumSharedLinkDto,
) { ) {
return this.albumService.createAlbumSharedLink(authUser, createAlbumShareLinkDto); return this.albumService.createAlbumSharedLink(authUser, createAlbumShareLinkDto);
} }

View File

@ -1,6 +1,6 @@
import { IsNotEmpty } from 'class-validator'; import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
export class AddAssetsDto { export class AddAssetsDto {
@IsNotEmpty() @ValidateUUID({ each: true })
assetIds!: string[]; assetIds!: string[];
} }

View File

@ -1,6 +1,6 @@
import { IsNotEmpty } from 'class-validator'; import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
export class AddUsersDto { export class AddUsersDto {
@IsNotEmpty() @ValidateUUID({ each: true })
sharedUserIds!: string[]; sharedUserIds!: string[];
} }

View File

@ -1,9 +1,6 @@
import { ApiProperty } from '@nestjs/swagger'; import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
import { IsNotEmpty, IsUUID } from 'class-validator';
export class AlbumIdDto { export class AlbumIdDto {
@IsNotEmpty() @ValidateUUID()
@IsUUID('4')
@ApiProperty({ format: 'uuid' })
albumId!: string; albumId!: string;
} }

View File

@ -1,27 +1,33 @@
import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger';
import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
import { IsBoolean, IsISO8601, IsOptional, IsString } from 'class-validator';
export class CreateAlbumShareLinkDto { export class CreateAlbumShareLinkDto {
@IsString() @ValidateUUID()
@IsNotEmpty()
albumId!: string; albumId!: string;
@IsString() @IsISO8601()
@IsOptional() @IsOptional()
@ApiProperty({ format: 'date-time' })
expiresAt?: string; expiresAt?: string;
@IsBoolean() @IsBoolean()
@IsOptional() @IsOptional()
@ApiProperty()
allowUpload?: boolean; allowUpload?: boolean;
@IsBoolean() @IsBoolean()
@IsOptional() @IsOptional()
@ApiProperty()
allowDownload?: boolean; allowDownload?: boolean;
@IsBoolean() @IsBoolean()
@IsOptional() @IsOptional()
@ApiProperty()
showExif?: boolean; showExif?: boolean;
@IsString() @IsString()
@IsOptional() @IsOptional()
@ApiProperty()
description?: string; description?: string;
} }

View File

@ -1,12 +1,16 @@
import { IsNotEmpty, IsOptional } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger';
import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
import { IsNotEmpty, IsString } from 'class-validator';
export class CreateAlbumDto { export class CreateAlbumDto {
@IsNotEmpty() @IsNotEmpty()
@IsString()
@ApiProperty()
albumName!: string; albumName!: string;
@IsOptional() @ValidateUUID({ optional: true, each: true })
sharedWithUserIds?: string[]; sharedWithUserIds?: string[];
@IsOptional() @ValidateUUID({ optional: true, each: true })
assetIds?: string[]; assetIds?: string[];
} }

View File

@ -1,6 +1,6 @@
import { IsNotEmpty } from 'class-validator'; import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
export class RemoveAssetsDto { export class RemoveAssetsDto {
@IsNotEmpty() @ValidateUUID({ each: true })
assetIds!: string[]; assetIds!: string[];
} }

View File

@ -1,9 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
import { IsOptional } from 'class-validator'; import { IsOptional } from 'class-validator';
export class UpdateAlbumDto { export class UpdateAlbumDto {
@IsOptional() @IsOptional()
@ApiProperty()
albumName?: string; albumName?: string;
@IsOptional() @ValidateUUID({ optional: true })
albumThumbnailAssetId?: string; albumThumbnailAssetId?: string;
} }

View File

@ -4,7 +4,7 @@ import { IsNumber, IsOptional, IsPositive, IsString } from 'class-validator';
export class DownloadDto { export class DownloadDto {
@IsOptional() @IsOptional()
@IsString() @IsString()
name = ''; name?: string;
@IsOptional() @IsOptional()
@IsPositive() @IsPositive()

View File

@ -0,0 +1,17 @@
import { applyDecorators } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
export type Options = {
optional?: boolean;
each?: boolean;
};
export function ValidateUUID({ optional, each }: Options = { optional: false, each: false }) {
return applyDecorators(
IsUUID('4', { each }),
ApiProperty({ format: 'uuid' }),
optional ? IsOptional() : IsNotEmpty(),
each ? IsArray() : IsString(),
);
}

View File

@ -1895,6 +1895,14 @@
"operationId": "downloadLibrary", "operationId": "downloadLibrary",
"description": "Current this is not used in any UI element", "description": "Current this is not used in any UI element",
"parameters": [ "parameters": [
{
"name": "name",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{ {
"name": "skip", "name": "skip",
"required": false, "required": false,
@ -3343,6 +3351,14 @@
"type": "string" "type": "string"
} }
}, },
{
"name": "name",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{ {
"name": "skip", "name": "skip",
"required": false, "required": false,
@ -5359,7 +5375,8 @@
"assetIds": { "assetIds": {
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string",
"format": "uuid"
} }
} }
}, },
@ -5373,7 +5390,8 @@
"assetIds": { "assetIds": {
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string",
"format": "uuid"
} }
} }
}, },
@ -5435,13 +5453,15 @@
"sharedWithUserIds": { "sharedWithUserIds": {
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string",
"format": "uuid"
} }
}, },
"assetIds": { "assetIds": {
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string",
"format": "uuid"
} }
} }
}, },
@ -5455,7 +5475,8 @@
"sharedUserIds": { "sharedUserIds": {
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string",
"format": "uuid"
} }
} }
}, },
@ -5491,7 +5512,8 @@
"type": "string" "type": "string"
}, },
"albumThumbnailAssetId": { "albumThumbnailAssetId": {
"type": "string" "type": "string",
"format": "uuid"
} }
} }
}, },
@ -5499,10 +5521,12 @@
"type": "object", "type": "object",
"properties": { "properties": {
"albumId": { "albumId": {
"type": "string" "type": "string",
"format": "uuid"
}, },
"expiresAt": { "expiresAt": {
"type": "string" "type": "string",
"format": "date-time"
}, },
"allowUpload": { "allowUpload": {
"type": "boolean" "type": "boolean"

View File

@ -1,7 +1,8 @@
import { Transform } from 'class-transformer'; import { Transform } from 'class-transformer';
import { IsBoolean, IsOptional, IsUUID } from 'class-validator'; import { IsBoolean, IsOptional } from 'class-validator';
import { toBoolean } from 'apps/immich/src/utils/transform.util'; import { toBoolean } from 'apps/immich/src/utils/transform.util';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
export class GetAlbumsDto { export class GetAlbumsDto {
@IsOptional() @IsOptional()
@ -20,8 +21,6 @@ export class GetAlbumsDto {
* Ignores the shared parameter * Ignores the shared parameter
* undefined: get all albums * undefined: get all albums
*/ */
@IsOptional() @ValidateUUID({ optional: true })
@IsUUID(4)
@ApiProperty({ format: 'uuid' })
assetId?: string; assetId?: string;
} }

View File

@ -3160,12 +3160,13 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration
/** /**
* *
* @param {string} albumId * @param {string} albumId
* @param {string} [name]
* @param {number} [skip] * @param {number} [skip]
* @param {string} [key] * @param {string} [key]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
downloadArchive: async (albumId: string, skip?: number, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { downloadArchive: async (albumId: string, name?: string, skip?: number, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'albumId' is not null or undefined // verify required parameter 'albumId' is not null or undefined
assertParamExists('downloadArchive', 'albumId', albumId) assertParamExists('downloadArchive', 'albumId', albumId)
const localVarPath = `/album/{albumId}/download` const localVarPath = `/album/{albumId}/download`
@ -3187,6 +3188,10 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration
// authentication cookie required // authentication cookie required
if (name !== undefined) {
localVarQueryParameter['name'] = name;
}
if (skip !== undefined) { if (skip !== undefined) {
localVarQueryParameter['skip'] = skip; localVarQueryParameter['skip'] = skip;
} }
@ -3529,13 +3534,14 @@ export const AlbumApiFp = function(configuration?: Configuration) {
/** /**
* *
* @param {string} albumId * @param {string} albumId
* @param {string} [name]
* @param {number} [skip] * @param {number} [skip]
* @param {string} [key] * @param {string} [key]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
async downloadArchive(albumId: string, skip?: number, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> { async downloadArchive(albumId: string, name?: string, skip?: number, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadArchive(albumId, skip, key, options); const localVarAxiosArgs = await localVarAxiosParamCreator.downloadArchive(albumId, name, skip, key, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/** /**
@ -3663,13 +3669,14 @@ export const AlbumApiFactory = function (configuration?: Configuration, basePath
/** /**
* *
* @param {string} albumId * @param {string} albumId
* @param {string} [name]
* @param {number} [skip] * @param {number} [skip]
* @param {string} [key] * @param {string} [key]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
downloadArchive(albumId: string, skip?: number, key?: string, options?: any): AxiosPromise<any> { downloadArchive(albumId: string, name?: string, skip?: number, key?: string, options?: any): AxiosPromise<any> {
return localVarFp.downloadArchive(albumId, skip, key, options).then((request) => request(axios, basePath)); return localVarFp.downloadArchive(albumId, name, skip, key, options).then((request) => request(axios, basePath));
}, },
/** /**
* *
@ -3800,14 +3807,15 @@ export class AlbumApi extends BaseAPI {
/** /**
* *
* @param {string} albumId * @param {string} albumId
* @param {string} [name]
* @param {number} [skip] * @param {number} [skip]
* @param {string} [key] * @param {string} [key]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
* @memberof AlbumApi * @memberof AlbumApi
*/ */
public downloadArchive(albumId: string, skip?: number, key?: string, options?: AxiosRequestConfig) { public downloadArchive(albumId: string, name?: string, skip?: number, key?: string, options?: AxiosRequestConfig) {
return AlbumApiFp(this.configuration).downloadArchive(albumId, skip, key, options).then((request) => request(this.axios, this.basePath)); return AlbumApiFp(this.configuration).downloadArchive(albumId, name, skip, key, options).then((request) => request(this.axios, this.basePath));
} }
/** /**
@ -4195,12 +4203,13 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
}, },
/** /**
* Current this is not used in any UI element * Current this is not used in any UI element
* @param {string} [name]
* @param {number} [skip] * @param {number} [skip]
* @param {string} [key] * @param {string} [key]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
downloadLibrary: async (skip?: number, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { downloadLibrary: async (name?: string, skip?: number, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/asset/download-library`; const localVarPath = `/asset/download-library`;
// use dummy base URL string because the URL constructor only accepts absolute URLs. // use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@ -4219,6 +4228,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
// authentication cookie required // authentication cookie required
if (name !== undefined) {
localVarQueryParameter['name'] = name;
}
if (skip !== undefined) { if (skip !== undefined) {
localVarQueryParameter['skip'] = skip; localVarQueryParameter['skip'] = skip;
} }
@ -5029,13 +5042,14 @@ export const AssetApiFp = function(configuration?: Configuration) {
}, },
/** /**
* Current this is not used in any UI element * Current this is not used in any UI element
* @param {string} [name]
* @param {number} [skip] * @param {number} [skip]
* @param {string} [key] * @param {string} [key]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
async downloadLibrary(skip?: number, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> { async downloadLibrary(name?: string, skip?: number, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadLibrary(skip, key, options); const localVarAxiosArgs = await localVarAxiosParamCreator.downloadLibrary(name, skip, key, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/** /**
@ -5284,13 +5298,14 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
}, },
/** /**
* Current this is not used in any UI element * Current this is not used in any UI element
* @param {string} [name]
* @param {number} [skip] * @param {number} [skip]
* @param {string} [key] * @param {string} [key]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
downloadLibrary(skip?: number, key?: string, options?: any): AxiosPromise<any> { downloadLibrary(name?: string, skip?: number, key?: string, options?: any): AxiosPromise<any> {
return localVarFp.downloadLibrary(skip, key, options).then((request) => request(axios, basePath)); return localVarFp.downloadLibrary(name, skip, key, options).then((request) => request(axios, basePath));
}, },
/** /**
* Get all AssetEntity belong to the user * Get all AssetEntity belong to the user
@ -5537,14 +5552,15 @@ export class AssetApi extends BaseAPI {
/** /**
* Current this is not used in any UI element * Current this is not used in any UI element
* @param {string} [name]
* @param {number} [skip] * @param {number} [skip]
* @param {string} [key] * @param {string} [key]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
* @memberof AssetApi * @memberof AssetApi
*/ */
public downloadLibrary(skip?: number, key?: string, options?: AxiosRequestConfig) { public downloadLibrary(name?: string, skip?: number, key?: string, options?: AxiosRequestConfig) {
return AssetApiFp(this.configuration).downloadLibrary(skip, key, options).then((request) => request(this.axios, this.basePath)); return AssetApiFp(this.configuration).downloadLibrary(name, skip, key, options).then((request) => request(this.axios, this.basePath));
} }
/** /**

View File

@ -264,6 +264,7 @@
const { data, status, headers } = await api.albumApi.downloadArchive( const { data, status, headers } = await api.albumApi.downloadArchive(
album.id, album.id,
undefined,
skip || undefined, skip || undefined,
sharedLink?.key, sharedLink?.key,
{ {