Browse Source

feat(server) Extend PUT /album/:id/assets endpoint (#857)

* Add new query parameter to API endpoint that allows adding assets to albums which potentially contain assets that are already part of this album.

* Change API endpoint

* Generate new APIs

* Fixed test

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Matthias Rupp 2 years ago
parent
commit
ea99567805

+ 3 - 1
.gitignore

@@ -1,3 +1,5 @@
 .DS_Store
 .vscode
-.idea
+.idea
+
+docker/upload

+ 2 - 2
mobile/openapi/.openapi-generator/FILES

@@ -3,6 +3,7 @@
 README.md
 analysis_options.yaml
 doc/AddAssetsDto.md
+doc/AddAssetsResponseDto.md
 doc/AddUsersDto.md
 doc/AdminSignupResponseDto.md
 doc/AlbumApi.md
@@ -82,6 +83,7 @@ lib/auth/http_basic_auth.dart
 lib/auth/http_bearer_auth.dart
 lib/auth/oauth.dart
 lib/model/add_assets_dto.dart
+lib/model/add_assets_response_dto.dart
 lib/model/add_users_dto.dart
 lib/model/admin_signup_response_dto.dart
 lib/model/album_count_response_dto.dart
@@ -137,5 +139,3 @@ lib/model/user_count_response_dto.dart
 lib/model/user_response_dto.dart
 lib/model/validate_access_token_response_dto.dart
 pubspec.yaml
-test/check_existing_assets_dto_test.dart
-test/check_existing_assets_response_dto_test.dart

+ 1 - 0
mobile/openapi/README.md

@@ -118,6 +118,7 @@ Class | Method | HTTP request | Description
 ## Documentation For Models
 
  - [AddAssetsDto](doc//AddAssetsDto.md)
+ - [AddAssetsResponseDto](doc//AddAssetsResponseDto.md)
  - [AddUsersDto](doc//AddUsersDto.md)
  - [AdminSignupResponseDto](doc//AdminSignupResponseDto.md)
  - [AlbumCountResponseDto](doc//AlbumCountResponseDto.md)

+ 17 - 0
mobile/openapi/doc/AddAssetsResponseDto.md

@@ -0,0 +1,17 @@
+# openapi.model.AddAssetsResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**successfullyAdded** | **int** |  | 
+**alreadyInAlbum** | **List<String>** |  | [default to const []]
+**album** | [**AlbumResponseDto**](AlbumResponseDto.md) |  | [optional] 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+

+ 2 - 2
mobile/openapi/doc/AlbumApi.md

@@ -22,7 +22,7 @@ Method | HTTP request | Description
 
 
 # **addAssetsToAlbum**
-> AlbumResponseDto addAssetsToAlbum(albumId, addAssetsDto)
+> AddAssetsResponseDto addAssetsToAlbum(albumId, addAssetsDto)
 
 
 
@@ -57,7 +57,7 @@ Name | Type | Description  | Notes
 
 ### Return type
 
-[**AlbumResponseDto**](AlbumResponseDto.md)
+[**AddAssetsResponseDto**](AddAssetsResponseDto.md)
 
 ### Authorization
 

+ 1 - 0
mobile/openapi/lib/api.dart

@@ -36,6 +36,7 @@ part 'api/server_info_api.dart';
 part 'api/user_api.dart';
 
 part 'model/add_assets_dto.dart';
+part 'model/add_assets_response_dto.dart';
 part 'model/add_users_dto.dart';
 part 'model/admin_signup_response_dto.dart';
 part 'model/album_count_response_dto.dart';

+ 2 - 2
mobile/openapi/lib/api/album_api.dart

@@ -53,7 +53,7 @@ class AlbumApi {
   /// * [String] albumId (required):
   ///
   /// * [AddAssetsDto] addAssetsDto (required):
-  Future<AlbumResponseDto?> addAssetsToAlbum(String albumId, AddAssetsDto addAssetsDto,) async {
+  Future<AddAssetsResponseDto?> addAssetsToAlbum(String albumId, AddAssetsDto addAssetsDto,) async {
     final response = await addAssetsToAlbumWithHttpInfo(albumId, addAssetsDto,);
     if (response.statusCode >= HttpStatus.badRequest) {
       throw ApiException(response.statusCode, await _decodeBodyBytes(response));
@@ -62,7 +62,7 @@ class AlbumApi {
     // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
     // FormatException when trying to decode an empty string.
     if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
-      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AlbumResponseDto',) as AlbumResponseDto;
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AddAssetsResponseDto',) as AddAssetsResponseDto;
     
     }
     return null;

+ 2 - 0
mobile/openapi/lib/api_client.dart

@@ -194,6 +194,8 @@ class ApiClient {
           return value is DateTime ? value : DateTime.tryParse(value);
         case 'AddAssetsDto':
           return AddAssetsDto.fromJson(value);
+        case 'AddAssetsResponseDto':
+          return AddAssetsResponseDto.fromJson(value);
         case 'AddUsersDto':
           return AddUsersDto.fromJson(value);
         case 'AdminSignupResponseDto':

+ 138 - 0
mobile/openapi/lib/model/add_assets_response_dto.dart

@@ -0,0 +1,138 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class AddAssetsResponseDto {
+  /// Returns a new [AddAssetsResponseDto] instance.
+  AddAssetsResponseDto({
+    required this.successfullyAdded,
+    this.alreadyInAlbum = const [],
+    this.album,
+  });
+
+  int successfullyAdded;
+
+  List<String> alreadyInAlbum;
+
+  ///
+  /// Please note: This property should have been non-nullable! Since the specification file
+  /// does not include a default value (using the "default:" property), however, the generated
+  /// source code must fall back to having a nullable type.
+  /// Consider adding a "default:" property in the specification file to hide this note.
+  ///
+  AlbumResponseDto? album;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is AddAssetsResponseDto &&
+     other.successfullyAdded == successfullyAdded &&
+     other.alreadyInAlbum == alreadyInAlbum &&
+     other.album == album;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (successfullyAdded.hashCode) +
+    (alreadyInAlbum.hashCode) +
+    (album == null ? 0 : album!.hashCode);
+
+  @override
+  String toString() => 'AddAssetsResponseDto[successfullyAdded=$successfullyAdded, alreadyInAlbum=$alreadyInAlbum, album=$album]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'successfullyAdded'] = successfullyAdded;
+      _json[r'alreadyInAlbum'] = alreadyInAlbum;
+    if (album != null) {
+      _json[r'album'] = album;
+    } else {
+      _json[r'album'] = null;
+    }
+    return _json;
+  }
+
+  /// Returns a new [AddAssetsResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static AddAssetsResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "AddAssetsResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "AddAssetsResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return AddAssetsResponseDto(
+        successfullyAdded: mapValueOfType<int>(json, r'successfullyAdded')!,
+        alreadyInAlbum: json[r'alreadyInAlbum'] is List
+            ? (json[r'alreadyInAlbum'] as List).cast<String>()
+            : const [],
+        album: AlbumResponseDto.fromJson(json[r'album']),
+      );
+    }
+    return null;
+  }
+
+  static List<AddAssetsResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <AddAssetsResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = AddAssetsResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, AddAssetsResponseDto> mapFromJson(dynamic json) {
+    final map = <String, AddAssetsResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AddAssetsResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of AddAssetsResponseDto-objects as value to a dart map
+  static Map<String, List<AddAssetsResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<AddAssetsResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AddAssetsResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'successfullyAdded',
+    'alreadyInAlbum',
+  };
+}
+

+ 37 - 0
mobile/openapi/test/add_assets_response_dto_test.dart

@@ -0,0 +1,37 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for AddAssetsResponseDto
+void main() {
+  // final instance = AddAssetsResponseDto();
+
+  group('test AddAssetsResponseDto', () {
+    // int successfullyAdded
+    test('to test the property `successfullyAdded`', () async {
+      // TODO
+    });
+
+    // List<String> alreadyInAlbum (default value: const [])
+    test('to test the property `alreadyInAlbum`', () async {
+      // TODO
+    });
+
+    // AlbumResponseDto album
+    test('to test the property `album`', () async {
+      // TODO
+    });
+
+
+  });
+
+}

+ 14 - 3
server/apps/immich/src/api-v1/album/album-repository.ts

@@ -11,6 +11,7 @@ import { GetAlbumsDto } from './dto/get-albums.dto';
 import { RemoveAssetsDto } from './dto/remove-assets.dto';
 import { UpdateAlbumDto } from './dto/update-album.dto';
 import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
+import {AddAssetsResponseDto} from "./response-dto/add-assets-response.dto";
 
 export interface IAlbumRepository {
   create(ownerId: string, createAlbumDto: CreateAlbumDto): Promise<AlbumEntity>;
@@ -20,7 +21,7 @@ export interface IAlbumRepository {
   addSharedUsers(album: AlbumEntity, addUsersDto: AddUsersDto): Promise<AlbumEntity>;
   removeUser(album: AlbumEntity, userId: string): Promise<void>;
   removeAssets(album: AlbumEntity, removeAssets: RemoveAssetsDto): Promise<AlbumEntity>;
-  addAssets(album: AlbumEntity, addAssetsDto: AddAssetsDto): Promise<AlbumEntity>;
+  addAssets(album: AlbumEntity, addAssetsDto: AddAssetsDto): Promise<AddAssetsResponseDto>;
   updateAlbum(album: AlbumEntity, updateAlbumDto: UpdateAlbumDto): Promise<AlbumEntity>;
   getListByAssetId(userId: string, assetId: string): Promise<AlbumEntity[]>;
   getCountByUserId(userId: string): Promise<AlbumCountResponseDto>;
@@ -260,10 +261,16 @@ export class AlbumRepository implements IAlbumRepository {
     }
   }
 
-  async addAssets(album: AlbumEntity, addAssetsDto: AddAssetsDto): Promise<AlbumEntity> {
+  async addAssets(album: AlbumEntity, addAssetsDto: AddAssetsDto): Promise<AddAssetsResponseDto> {
     const newRecords: AssetAlbumEntity[] = [];
+    const alreadyExisting: string[] = [];
 
     for (const assetId of addAssetsDto.assetIds) {
+      // Album already contains that asset
+      if (album.assets?.some(a => a.assetId === assetId)) {
+        alreadyExisting.push(assetId);
+        continue;
+      }
       const newAssetAlbum = new AssetAlbumEntity();
       newAssetAlbum.assetId = assetId;
       newAssetAlbum.albumId = album.id;
@@ -278,7 +285,11 @@ export class AlbumRepository implements IAlbumRepository {
     }
 
     await this.assetAlbumRepository.save([...newRecords]);
-    return this.get(album.id) as Promise<AlbumEntity>; // There is an album for sure
+
+    return {
+      successfullyAdded: newRecords.length,
+      alreadyInAlbum: alreadyExisting
+    };
   }
 
   updateAlbum(album: AlbumEntity, updateAlbumDto: UpdateAlbumDto): Promise<AlbumEntity> {

+ 2 - 1
server/apps/immich/src/api-v1/album/album.controller.ts

@@ -24,6 +24,7 @@ import { GetAlbumsDto } from './dto/get-albums.dto';
 import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
 import { AlbumResponseDto } from './response-dto/album-response.dto';
 import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
+import {AddAssetsResponseDto} from "./response-dto/add-assets-response.dto";
 
 // TODO might be worth creating a AlbumParamsDto that validates `albumId` instead of using the pipe.
 @Authenticated()
@@ -57,7 +58,7 @@ export class AlbumController {
     @GetAuthUser() authUser: AuthUserDto,
     @Body(ValidationPipe) addAssetsDto: AddAssetsDto,
     @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
-  ) {
+  ) : Promise<AddAssetsResponseDto> {
     return this.albumService.addAssetsToAlbum(authUser, addAssetsDto, albumId);
   }
 

+ 34 - 9
server/apps/immich/src/api-v1/album/album.service.spec.ts

@@ -1,10 +1,11 @@
 import { AlbumService } from './album.service';
-import { IAlbumRepository } from './album-repository';
 import { AuthUserDto } from '../../decorators/auth-user.decorator';
 import { BadRequestException, NotFoundException, ForbiddenException } from '@nestjs/common';
 import { AlbumEntity } from '@app/database/entities/album.entity';
 import { AlbumResponseDto } from './response-dto/album-response.dto';
 import { IAssetRepository } from '../asset/asset-repository';
+import {AddAssetsResponseDto} from "./response-dto/add-assets-response.dto";
+import {IAlbumRepository} from "./album-repository";
 
 describe('Album service', () => {
   let sut: AlbumService;
@@ -329,10 +330,16 @@ describe('Album service', () => {
 
   it('adds assets to owned album', async () => {
     const albumEntity = _getOwnedAlbum();
+
+    const albumResponse: AddAssetsResponseDto = {
+      alreadyInAlbum: [],
+      successfullyAdded: 1
+    };
+
     const albumId = albumEntity.id;
 
     albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
-    albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
+    albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AddAssetsResponseDto>(albumResponse));
 
     const result = await sut.addAssetsToAlbum(
       authUser,
@@ -340,18 +347,24 @@ describe('Album service', () => {
         assetIds: ['1'],
       },
       albumId,
-    );
+    ) as AddAssetsResponseDto;
 
     // TODO: stub and expect album rendered
-    expect(result.id).toEqual(albumId);
+    expect(result.album?.id).toEqual(albumId);
   });
 
   it('adds assets to shared album (shared with auth user)', async () => {
     const albumEntity = _getSharedWithAuthUserAlbum();
+
+    const albumResponse: AddAssetsResponseDto = {
+      alreadyInAlbum: [],
+      successfullyAdded: 1
+    };
+
     const albumId = albumEntity.id;
 
     albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
-    albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
+    albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AddAssetsResponseDto>(albumResponse));
 
     const result = await sut.addAssetsToAlbum(
       authUser,
@@ -359,18 +372,24 @@ describe('Album service', () => {
         assetIds: ['1'],
       },
       albumId,
-    );
+    ) as AddAssetsResponseDto;
 
     // TODO: stub and expect album rendered
-    expect(result.id).toEqual(albumId);
+    expect(result.album?.id).toEqual(albumId);
   });
 
   it('prevents adding assets to a not owned / shared album', async () => {
     const albumEntity = _getNotOwnedNotSharedAlbum();
+
+    const albumResponse: AddAssetsResponseDto = {
+      alreadyInAlbum: [],
+      successfullyAdded: 1
+    };
+
     const albumId = albumEntity.id;
 
     albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
-    albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
+    albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AddAssetsResponseDto>(albumResponse));
 
     expect(
       sut.addAssetsToAlbum(
@@ -425,10 +444,16 @@ describe('Album service', () => {
 
   it('prevents removing assets from a not owned / shared album', async () => {
     const albumEntity = _getNotOwnedNotSharedAlbum();
+
+    const albumResponse: AddAssetsResponseDto = {
+      alreadyInAlbum: [],
+      successfullyAdded: 1
+    };
+
     const albumId = albumEntity.id;
 
     albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
-    albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
+    albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AddAssetsResponseDto>(albumResponse));
 
     expect(
       sut.removeAssetsFromAlbum(

+ 11 - 5
server/apps/immich/src/api-v1/album/album.service.ts

@@ -1,8 +1,7 @@
 import { BadRequestException, Inject, Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
 import { AuthUserDto } from '../../decorators/auth-user.decorator';
-import { AddAssetsDto } from './dto/add-assets.dto';
 import { CreateAlbumDto } from './dto/create-album.dto';
-import { AlbumEntity } from '../../../../../libs/database/src/entities/album.entity';
+import { AlbumEntity } from '@app/database/entities/album.entity';
 import { AddUsersDto } from './dto/add-users.dto';
 import { RemoveAssetsDto } from './dto/remove-assets.dto';
 import { UpdateAlbumDto } from './dto/update-album.dto';
@@ -11,6 +10,8 @@ import { AlbumResponseDto, mapAlbum, mapAlbumExcludeAssetInfo } from './response
 import { ALBUM_REPOSITORY, IAlbumRepository } from './album-repository';
 import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
 import { ASSET_REPOSITORY, IAssetRepository } from '../asset/asset-repository';
+import { AddAssetsResponseDto } from "./response-dto/add-assets-response.dto";
+import {AddAssetsDto} from "./dto/add-assets.dto";
 
 @Injectable()
 export class AlbumService {
@@ -108,10 +109,15 @@ export class AlbumService {
     authUser: AuthUserDto,
     addAssetsDto: AddAssetsDto,
     albumId: string,
-  ): Promise<AlbumResponseDto> {
+  ): Promise<AddAssetsResponseDto> {
     const album = await this._getAlbum({ authUser, albumId, validateIsOwner: false });
-    const updatedAlbum = await this._albumRepository.addAssets(album, addAssetsDto);
-    return mapAlbum(updatedAlbum);
+    const result = await this._albumRepository.addAssets(album, addAssetsDto);
+    const newAlbum = await this._getAlbum({ authUser, albumId, validateIsOwner: false });
+
+    return {
+      ...result,
+      album: mapAlbum(newAlbum)
+    };
   }
 
   async updateAlbumInfo(

+ 13 - 0
server/apps/immich/src/api-v1/album/response-dto/add-assets-response.dto.ts

@@ -0,0 +1,13 @@
+import {ApiProperty} from "@nestjs/swagger";
+import {AlbumResponseDto} from "./album-response.dto";
+
+export class AddAssetsResponseDto {
+    @ApiProperty({ type: 'integer' })
+    successfullyAdded!: number;
+
+    @ApiProperty()
+    alreadyInAlbum!: string[];
+
+    @ApiProperty()
+    album?: AlbumResponseDto;
+}

File diff suppressed because it is too large
+ 0 - 0
server/immich-openapi-specs.json


+ 27 - 2
web/src/api/open-api/api.ts

@@ -34,6 +34,31 @@ export interface AddAssetsDto {
      */
     'assetIds': Array<string>;
 }
+/**
+ * 
+ * @export
+ * @interface AddAssetsResponseDto
+ */
+export interface AddAssetsResponseDto {
+    /**
+     * 
+     * @type {number}
+     * @memberof AddAssetsResponseDto
+     */
+    'successfullyAdded': number;
+    /**
+     * 
+     * @type {Array<string>}
+     * @memberof AddAssetsResponseDto
+     */
+    'alreadyInAlbum': Array<string>;
+    /**
+     * 
+     * @type {AlbumResponseDto}
+     * @memberof AddAssetsResponseDto
+     */
+    'album'?: AlbumResponseDto;
+}
 /**
  * 
  * @export
@@ -1990,7 +2015,7 @@ export const AlbumApiFp = function(configuration?: Configuration) {
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async addAssetsToAlbum(albumId: string, addAssetsDto: AddAssetsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AlbumResponseDto>> {
+        async addAssetsToAlbum(albumId: string, addAssetsDto: AddAssetsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AddAssetsResponseDto>> {
             const localVarAxiosArgs = await localVarAxiosParamCreator.addAssetsToAlbum(albumId, addAssetsDto, options);
             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
         },
@@ -2105,7 +2130,7 @@ export const AlbumApiFactory = function (configuration?: Configuration, basePath
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        addAssetsToAlbum(albumId: string, addAssetsDto: AddAssetsDto, options?: any): AxiosPromise<AlbumResponseDto> {
+        addAssetsToAlbum(albumId: string, addAssetsDto: AddAssetsDto, options?: any): AxiosPromise<AddAssetsResponseDto> {
             return localVarFp.addAssetsToAlbum(albumId, addAssetsDto, options).then((request) => request(axios, basePath));
         },
         /**

+ 7 - 2
web/src/lib/components/album-page/album-viewer.svelte

@@ -215,8 +215,10 @@
 			const { data } = await api.albumApi.addAssetsToAlbum(album.id, {
 				assetIds: assets.map((a) => a.id)
 			});
-			album = data;
 
+			if (data.album) {
+				album = data.album;
+			}
 			isShowAssetSelection = false;
 		} catch (e) {
 			console.error('Error [createAlbumHandler] ', e);
@@ -233,7 +235,10 @@
 			const { data } = await api.albumApi.addAssetsToAlbum(album.id, {
 				assetIds: assetIds
 			});
-			album = data;
+
+			if (data.album) {
+				album = data.album;
+			}
 		} catch (e) {
 			console.error('Error [assetUploadedToAlbumHandler] ', e);
 			notificationController.show({

Some files were not shown because too many files changed in this diff