mirror of
https://github.com/immich-app/immich.git
synced 2025-04-21 07:26:25 +02:00
feat(server): improve validation of albums (#2188)
* feat(server): improve validation of albums * regenerate openapi + fix downloadArchive for web
This commit is contained in:
parent
b03ce897c7
commit
8e3a7caebd
23 changed files with 164 additions and 83 deletions
mobile/openapi
doc
lib
test
server
apps/immich/src
immich-openapi-specs.jsonlibs/domain/src/album/dto
web/src
6
mobile/openapi/doc/AlbumApi.md
generated
6
mobile/openapi/doc/AlbumApi.md
generated
|
@ -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)
|
||||
|
||||
# **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 albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final name = name_example; // String |
|
||||
final skip = 8.14; // num |
|
||||
final key = key_example; // String |
|
||||
|
||||
try {
|
||||
final result = api_instance.downloadArchive(albumId, skip, key);
|
||||
final result = api_instance.downloadArchive(albumId, name, skip, key);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AlbumApi->downloadArchive: $e\n');
|
||||
|
@ -332,6 +333,7 @@ try {
|
|||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**albumId** | **String**| |
|
||||
**name** | **String**| | [optional]
|
||||
**skip** | **num**| | [optional]
|
||||
**key** | **String**| | [optional]
|
||||
|
||||
|
|
6
mobile/openapi/doc/AssetApi.md
generated
6
mobile/openapi/doc/AssetApi.md
generated
|
@ -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)
|
||||
|
||||
# **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';
|
||||
|
||||
final api_instance = AssetApi();
|
||||
final name = name_example; // String |
|
||||
final skip = 8.14; // num |
|
||||
final key = key_example; // String |
|
||||
|
||||
try {
|
||||
final result = api_instance.downloadLibrary(skip, key);
|
||||
final result = api_instance.downloadLibrary(name, skip, key);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->downloadLibrary: $e\n');
|
||||
|
@ -450,6 +451,7 @@ try {
|
|||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**name** | **String**| | [optional]
|
||||
**skip** | **num**| | [optional]
|
||||
**key** | **String**| | [optional]
|
||||
|
||||
|
|
2
mobile/openapi/doc/CreateAlbumShareLinkDto.md
generated
2
mobile/openapi/doc/CreateAlbumShareLinkDto.md
generated
|
@ -9,7 +9,7 @@ import 'package:openapi/api.dart';
|
|||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**albumId** | **String** | |
|
||||
**expiresAt** | **String** | | [optional]
|
||||
**expiresAt** | [**DateTime**](DateTime.md) | | [optional]
|
||||
**allowUpload** | **bool** | | [optional]
|
||||
**allowDownload** | **bool** | | [optional]
|
||||
**showExif** | **bool** | | [optional]
|
||||
|
|
13
mobile/openapi/lib/api/album_api.dart
generated
13
mobile/openapi/lib/api/album_api.dart
generated
|
@ -295,10 +295,12 @@ class AlbumApi {
|
|||
///
|
||||
/// * [String] albumId (required):
|
||||
///
|
||||
/// * [String] name:
|
||||
///
|
||||
/// * [num] skip:
|
||||
///
|
||||
/// * [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
|
||||
final path = r'/album/{albumId}/download'
|
||||
.replaceAll('{albumId}', albumId);
|
||||
|
@ -310,6 +312,9 @@ class AlbumApi {
|
|||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
if (name != null) {
|
||||
queryParams.addAll(_queryParams('', 'name', name));
|
||||
}
|
||||
if (skip != null) {
|
||||
queryParams.addAll(_queryParams('', 'skip', skip));
|
||||
}
|
||||
|
@ -337,11 +342,13 @@ class AlbumApi {
|
|||
///
|
||||
/// * [String] albumId (required):
|
||||
///
|
||||
/// * [String] name:
|
||||
///
|
||||
/// * [num] skip:
|
||||
///
|
||||
/// * [String] key:
|
||||
Future<MultipartFile?> downloadArchive(String albumId, { num? skip, String? key, }) async {
|
||||
final response = await downloadArchiveWithHttpInfo(albumId, skip: skip, key: key, );
|
||||
Future<MultipartFile?> downloadArchive(String albumId, { String? name, num? skip, String? key, }) async {
|
||||
final response = await downloadArchiveWithHttpInfo(albumId, name: name, skip: skip, key: key, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
|
13
mobile/openapi/lib/api/asset_api.dart
generated
13
mobile/openapi/lib/api/asset_api.dart
generated
|
@ -422,10 +422,12 @@ class AssetApi {
|
|||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] name:
|
||||
///
|
||||
/// * [num] skip:
|
||||
///
|
||||
/// * [String] key:
|
||||
Future<Response> downloadLibraryWithHttpInfo({ num? skip, String? key, }) async {
|
||||
Future<Response> downloadLibraryWithHttpInfo({ String? name, num? skip, String? key, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset/download-library';
|
||||
|
||||
|
@ -436,6 +438,9 @@ class AssetApi {
|
|||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
if (name != null) {
|
||||
queryParams.addAll(_queryParams('', 'name', name));
|
||||
}
|
||||
if (skip != null) {
|
||||
queryParams.addAll(_queryParams('', 'skip', skip));
|
||||
}
|
||||
|
@ -461,11 +466,13 @@ class AssetApi {
|
|||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] name:
|
||||
///
|
||||
/// * [num] skip:
|
||||
///
|
||||
/// * [String] key:
|
||||
Future<MultipartFile?> downloadLibrary({ num? skip, String? key, }) async {
|
||||
final response = await downloadLibraryWithHttpInfo( skip: skip, key: key, );
|
||||
Future<MultipartFile?> downloadLibrary({ String? name, num? skip, String? key, }) async {
|
||||
final response = await downloadLibraryWithHttpInfo( name: name, skip: skip, key: key, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ class CreateAlbumShareLinkDto {
|
|||
/// source code must fall back to having a nullable type.
|
||||
/// 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
|
||||
|
@ -89,7 +89,7 @@ class CreateAlbumShareLinkDto {
|
|||
final json = <String, dynamic>{};
|
||||
json[r'albumId'] = this.albumId;
|
||||
if (this.expiresAt != null) {
|
||||
json[r'expiresAt'] = this.expiresAt;
|
||||
json[r'expiresAt'] = this.expiresAt!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'expiresAt'] = null;
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ class CreateAlbumShareLinkDto {
|
|||
|
||||
return CreateAlbumShareLinkDto(
|
||||
albumId: mapValueOfType<String>(json, r'albumId')!,
|
||||
expiresAt: mapValueOfType<String>(json, r'expiresAt'),
|
||||
expiresAt: mapDateTime(json, r'expiresAt', ''),
|
||||
allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
|
||||
allowDownload: mapValueOfType<bool>(json, r'allowDownload'),
|
||||
showExif: mapValueOfType<bool>(json, r'showExif'),
|
||||
|
|
2
mobile/openapi/test/album_api_test.dart
generated
2
mobile/openapi/test/album_api_test.dart
generated
|
@ -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 {
|
||||
// TODO
|
||||
});
|
||||
|
|
2
mobile/openapi/test/asset_api_test.dart
generated
2
mobile/openapi/test/asset_api_test.dart
generated
|
@ -68,7 +68,7 @@ void main() {
|
|||
|
||||
// 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 {
|
||||
// TODO
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
// String expiresAt
|
||||
// DateTime expiresAt
|
||||
test('to test the property `expiresAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
|
|
@ -1,16 +1,4 @@
|
|||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
ValidationPipe,
|
||||
Put,
|
||||
Query,
|
||||
Response,
|
||||
} from '@nestjs/common';
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, Put, Query, Response } from '@nestjs/common';
|
||||
import { ParseMeUUIDPipe } from '../validation/parse-me-uuid-pipe';
|
||||
import { AlbumService } from './album.service';
|
||||
import { CreateAlbumDto } from './dto/create-album.dto';
|
||||
|
@ -33,9 +21,11 @@ import {
|
|||
import { DownloadDto } from '../asset/dto/download-library.dto';
|
||||
import { CreateAlbumShareLinkDto as CreateAlbumSharedLinkDto } from './dto/create-album-shared-link.dto';
|
||||
import { AlbumIdDto } from './dto/album-id.dto';
|
||||
import { UseValidation } from '../../decorators/use-validation.decorator';
|
||||
|
||||
@ApiTags('Album')
|
||||
@Controller('album')
|
||||
@UseValidation()
|
||||
export class AlbumController {
|
||||
constructor(private readonly albumService: AlbumService) {}
|
||||
|
||||
|
@ -47,7 +37,8 @@ export class AlbumController {
|
|||
|
||||
@Authenticated()
|
||||
@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);
|
||||
}
|
||||
|
||||
|
@ -55,9 +46,10 @@ export class AlbumController {
|
|||
@Put('/:albumId/users')
|
||||
async addUsersToAlbum(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) addUsersDto: AddUsersDto,
|
||||
@Body() addUsersDto: AddUsersDto,
|
||||
@Param() { albumId }: AlbumIdDto,
|
||||
) {
|
||||
// TODO: Handle nonexistent sharedUserIds.
|
||||
return this.albumService.addUsersToAlbum(authUser, addUsersDto, albumId);
|
||||
}
|
||||
|
||||
|
@ -65,9 +57,11 @@ export class AlbumController {
|
|||
@Put('/:albumId/assets')
|
||||
async addAssetsToAlbum(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) addAssetsDto: AddAssetsDto,
|
||||
@Body() addAssetsDto: AddAssetsDto,
|
||||
@Param() { albumId }: AlbumIdDto,
|
||||
): Promise<AddAssetsResponseDto> {
|
||||
// TODO: Handle nonexistent assetIds.
|
||||
// TODO: Disallow adding assets of another user to an album.
|
||||
return this.albumService.addAssetsToAlbum(authUser, addAssetsDto, albumId);
|
||||
}
|
||||
|
||||
|
@ -81,7 +75,7 @@ export class AlbumController {
|
|||
@Delete('/:albumId/assets')
|
||||
async removeAssetFromAlbum(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) removeAssetsDto: RemoveAssetsDto,
|
||||
@Body() removeAssetsDto: RemoveAssetsDto,
|
||||
@Param() { albumId }: AlbumIdDto,
|
||||
): Promise<AlbumResponseDto> {
|
||||
return this.albumService.removeAssetsFromAlbum(authUser, removeAssetsDto, albumId);
|
||||
|
@ -107,9 +101,11 @@ export class AlbumController {
|
|||
@Patch('/:albumId')
|
||||
async updateAlbumInfo(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) updateAlbumInfoDto: UpdateAlbumDto,
|
||||
@Body() updateAlbumInfoDto: UpdateAlbumDto,
|
||||
@Param() { albumId }: AlbumIdDto,
|
||||
) {
|
||||
// TODO: Handle nonexistent albumThumbnailAssetId.
|
||||
// TODO: Disallow setting asset from other user as albumThumbnailAssetId.
|
||||
return this.albumService.updateAlbumInfo(authUser, updateAlbumInfoDto, albumId);
|
||||
}
|
||||
|
||||
|
@ -119,7 +115,7 @@ export class AlbumController {
|
|||
async downloadArchive(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Param() { albumId }: AlbumIdDto,
|
||||
@Query(new ValidationPipe({ transform: true })) dto: DownloadDto,
|
||||
@Query() dto: DownloadDto,
|
||||
@Response({ passthrough: true }) res: Res,
|
||||
) {
|
||||
this.albumService.checkDownloadAccess(authUser);
|
||||
|
@ -140,7 +136,7 @@ export class AlbumController {
|
|||
@Post('/create-shared-link')
|
||||
async createAlbumSharedLink(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) createAlbumShareLinkDto: CreateAlbumSharedLinkDto,
|
||||
@Body() createAlbumShareLinkDto: CreateAlbumSharedLinkDto,
|
||||
) {
|
||||
return this.albumService.createAlbumSharedLink(authUser, createAlbumShareLinkDto);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { IsNotEmpty } from 'class-validator';
|
||||
import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
|
||||
|
||||
export class AddAssetsDto {
|
||||
@IsNotEmpty()
|
||||
@ValidateUUID({ each: true })
|
||||
assetIds!: string[];
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { IsNotEmpty } from 'class-validator';
|
||||
import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
|
||||
|
||||
export class AddUsersDto {
|
||||
@IsNotEmpty()
|
||||
@ValidateUUID({ each: true })
|
||||
sharedUserIds!: string[];
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsUUID } from 'class-validator';
|
||||
import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
|
||||
|
||||
export class AlbumIdDto {
|
||||
@IsNotEmpty()
|
||||
@IsUUID('4')
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
@ValidateUUID()
|
||||
albumId!: string;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ValidateUUID()
|
||||
albumId!: string;
|
||||
|
||||
@IsString()
|
||||
@IsISO8601()
|
||||
@IsOptional()
|
||||
@ApiProperty({ format: 'date-time' })
|
||||
expiresAt?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
allowUpload?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
allowDownload?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
showExif?: boolean;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
description?: string;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
albumName!: string;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateUUID({ optional: true, each: true })
|
||||
sharedWithUserIds?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@ValidateUUID({ optional: true, each: true })
|
||||
assetIds?: string[];
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { IsNotEmpty } from 'class-validator';
|
||||
import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
|
||||
|
||||
export class RemoveAssetsDto {
|
||||
@IsNotEmpty()
|
||||
@ValidateUUID({ each: true })
|
||||
assetIds!: string[];
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
|
||||
import { IsOptional } from 'class-validator';
|
||||
|
||||
export class UpdateAlbumDto {
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
albumName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateUUID({ optional: true })
|
||||
albumThumbnailAssetId?: string;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { IsNumber, IsOptional, IsPositive, IsString } from 'class-validator';
|
|||
export class DownloadDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name = '';
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsPositive()
|
||||
|
|
17
server/apps/immich/src/decorators/validate-uuid.decorator.ts
Normal file
17
server/apps/immich/src/decorators/validate-uuid.decorator.ts
Normal 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(),
|
||||
);
|
||||
}
|
|
@ -1895,6 +1895,14 @@
|
|||
"operationId": "downloadLibrary",
|
||||
"description": "Current this is not used in any UI element",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"required": false,
|
||||
|
@ -3343,6 +3351,14 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"required": false,
|
||||
|
@ -5359,7 +5375,8 @@
|
|||
"assetIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5373,7 +5390,8 @@
|
|||
"assetIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5435,13 +5453,15 @@
|
|||
"sharedWithUserIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
},
|
||||
"assetIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5455,7 +5475,8 @@
|
|||
"sharedUserIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5491,7 +5512,8 @@
|
|||
"type": "string"
|
||||
},
|
||||
"albumThumbnailAssetId": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5499,10 +5521,12 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"albumId": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"expiresAt": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"allowUpload": {
|
||||
"type": "boolean"
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
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 { ApiProperty } from '@nestjs/swagger';
|
||||
import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
|
||||
|
||||
export class GetAlbumsDto {
|
||||
@IsOptional()
|
||||
|
@ -20,8 +21,6 @@ export class GetAlbumsDto {
|
|||
* Ignores the shared parameter
|
||||
* undefined: get all albums
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsUUID(4)
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
@ValidateUUID({ optional: true })
|
||||
assetId?: string;
|
||||
}
|
||||
|
|
44
web/src/api/open-api/api.ts
generated
44
web/src/api/open-api/api.ts
generated
|
@ -3160,12 +3160,13 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration
|
|||
/**
|
||||
*
|
||||
* @param {string} albumId
|
||||
* @param {string} [name]
|
||||
* @param {number} [skip]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @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
|
||||
assertParamExists('downloadArchive', 'albumId', albumId)
|
||||
const localVarPath = `/album/{albumId}/download`
|
||||
|
@ -3187,6 +3188,10 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration
|
|||
|
||||
// authentication cookie required
|
||||
|
||||
if (name !== undefined) {
|
||||
localVarQueryParameter['name'] = name;
|
||||
}
|
||||
|
||||
if (skip !== undefined) {
|
||||
localVarQueryParameter['skip'] = skip;
|
||||
}
|
||||
|
@ -3529,13 +3534,14 @@ export const AlbumApiFp = function(configuration?: Configuration) {
|
|||
/**
|
||||
*
|
||||
* @param {string} albumId
|
||||
* @param {string} [name]
|
||||
* @param {number} [skip]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async downloadArchive(albumId: string, skip?: number, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadArchive(albumId, skip, key, options);
|
||||
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, name, skip, key, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
|
@ -3663,13 +3669,14 @@ export const AlbumApiFactory = function (configuration?: Configuration, basePath
|
|||
/**
|
||||
*
|
||||
* @param {string} albumId
|
||||
* @param {string} [name]
|
||||
* @param {number} [skip]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
downloadArchive(albumId: string, skip?: number, key?: string, options?: any): AxiosPromise<any> {
|
||||
return localVarFp.downloadArchive(albumId, skip, key, options).then((request) => request(axios, basePath));
|
||||
downloadArchive(albumId: string, name?: string, skip?: number, key?: string, options?: any): AxiosPromise<any> {
|
||||
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} [name]
|
||||
* @param {number} [skip]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AlbumApi
|
||||
*/
|
||||
public downloadArchive(albumId: string, skip?: number, key?: string, options?: AxiosRequestConfig) {
|
||||
return AlbumApiFp(this.configuration).downloadArchive(albumId, skip, key, options).then((request) => request(this.axios, this.basePath));
|
||||
public downloadArchive(albumId: string, name?: string, skip?: number, key?: string, options?: AxiosRequestConfig) {
|
||||
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
|
||||
* @param {string} [name]
|
||||
* @param {number} [skip]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @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`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
|
@ -4219,6 +4228,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
|
||||
// authentication cookie required
|
||||
|
||||
if (name !== undefined) {
|
||||
localVarQueryParameter['name'] = name;
|
||||
}
|
||||
|
||||
if (skip !== undefined) {
|
||||
localVarQueryParameter['skip'] = skip;
|
||||
}
|
||||
|
@ -5029,13 +5042,14 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||
},
|
||||
/**
|
||||
* Current this is not used in any UI element
|
||||
* @param {string} [name]
|
||||
* @param {number} [skip]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async downloadLibrary(skip?: number, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadLibrary(skip, key, options);
|
||||
async downloadLibrary(name?: string, skip?: number, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadLibrary(name, skip, key, options);
|
||||
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
|
||||
* @param {string} [name]
|
||||
* @param {number} [skip]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
downloadLibrary(skip?: number, key?: string, options?: any): AxiosPromise<any> {
|
||||
return localVarFp.downloadLibrary(skip, key, options).then((request) => request(axios, basePath));
|
||||
downloadLibrary(name?: string, skip?: number, key?: string, options?: any): AxiosPromise<any> {
|
||||
return localVarFp.downloadLibrary(name, skip, key, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* 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
|
||||
* @param {string} [name]
|
||||
* @param {number} [skip]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssetApi
|
||||
*/
|
||||
public downloadLibrary(skip?: number, key?: string, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).downloadLibrary(skip, key, options).then((request) => request(this.axios, this.basePath));
|
||||
public downloadLibrary(name?: string, skip?: number, key?: string, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).downloadLibrary(name, skip, key, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -264,6 +264,7 @@
|
|||
|
||||
const { data, status, headers } = await api.albumApi.downloadArchive(
|
||||
album.id,
|
||||
undefined,
|
||||
skip || undefined,
|
||||
sharedLink?.key,
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue