mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	refactor(server)!: add/remove album assets (#3109)
* refactor: add/remove album assets * chore: open api * feat: remove owned assets from album * refactor: move to bulk id req/res dto * chore: open api * chore: merge main * dev: mobile work * fix: adding asset from web not sync with mobile * remove print statement --------- Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
This commit is contained in:
		
							
								
								
									
										120
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										120
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -99,44 +99,6 @@ export interface APIKeyUpdateDto { | ||||
|      */ | ||||
|     'name': string; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface AddAssetsDto | ||||
|  */ | ||||
| export interface AddAssetsDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {Array<string>} | ||||
|      * @memberof AddAssetsDto | ||||
|      */ | ||||
|     'assetIds': Array<string>; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface AddAssetsResponseDto | ||||
|  */ | ||||
| export interface AddAssetsResponseDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {AlbumResponseDto} | ||||
|      * @memberof AddAssetsResponseDto | ||||
|      */ | ||||
|     'album'?: AlbumResponseDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {Array<string>} | ||||
|      * @memberof AddAssetsResponseDto | ||||
|      */ | ||||
|     'alreadyInAlbum': Array<string>; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {number} | ||||
|      * @memberof AddAssetsResponseDto | ||||
|      */ | ||||
|     'successfullyAdded': number; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @@ -821,6 +783,19 @@ export const BulkIdResponseDtoErrorEnum = { | ||||
| 
 | ||||
| export type BulkIdResponseDtoErrorEnum = typeof BulkIdResponseDtoErrorEnum[keyof typeof BulkIdResponseDtoErrorEnum]; | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface BulkIdsDto | ||||
|  */ | ||||
| export interface BulkIdsDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {Array<string>} | ||||
|      * @memberof BulkIdsDto | ||||
|      */ | ||||
|     'ids': Array<string>; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @@ -1927,19 +1902,6 @@ export interface QueueStatusDto { | ||||
|      */ | ||||
|     'isPaused': boolean; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface RemoveAssetsDto | ||||
|  */ | ||||
| export interface RemoveAssetsDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {Array<string>} | ||||
|      * @memberof RemoveAssetsDto | ||||
|      */ | ||||
|     'assetIds': Array<string>; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @@ -3679,16 +3641,16 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} id  | ||||
|          * @param {AddAssetsDto} addAssetsDto  | ||||
|          * @param {BulkIdsDto} bulkIdsDto  | ||||
|          * @param {string} [key]  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         addAssetsToAlbum: async (id: string, addAssetsDto: AddAssetsDto, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|         addAssetsToAlbum: async (id: string, bulkIdsDto: BulkIdsDto, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|             // verify required parameter 'id' is not null or undefined
 | ||||
|             assertParamExists('addAssetsToAlbum', 'id', id) | ||||
|             // verify required parameter 'addAssetsDto' is not null or undefined
 | ||||
|             assertParamExists('addAssetsToAlbum', 'addAssetsDto', addAssetsDto) | ||||
|             // verify required parameter 'bulkIdsDto' is not null or undefined
 | ||||
|             assertParamExists('addAssetsToAlbum', 'bulkIdsDto', bulkIdsDto) | ||||
|             const localVarPath = `/album/{id}/assets` | ||||
|                 .replace(`{${"id"}}`, encodeURIComponent(String(id))); | ||||
|             // use dummy base URL string because the URL constructor only accepts absolute URLs.
 | ||||
| @@ -3722,7 +3684,7 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration | ||||
|             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||
|             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||
|             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||
|             localVarRequestOptions.data = serializeDataIfNeeded(addAssetsDto, localVarRequestOptions, configuration) | ||||
|             localVarRequestOptions.data = serializeDataIfNeeded(bulkIdsDto, localVarRequestOptions, configuration) | ||||
| 
 | ||||
|             return { | ||||
|                 url: toPathString(localVarUrlObj), | ||||
| @@ -3999,15 +3961,15 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} id  | ||||
|          * @param {RemoveAssetsDto} removeAssetsDto  | ||||
|          * @param {BulkIdsDto} bulkIdsDto  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         removeAssetFromAlbum: async (id: string, removeAssetsDto: RemoveAssetsDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|         removeAssetFromAlbum: async (id: string, bulkIdsDto: BulkIdsDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|             // verify required parameter 'id' is not null or undefined
 | ||||
|             assertParamExists('removeAssetFromAlbum', 'id', id) | ||||
|             // verify required parameter 'removeAssetsDto' is not null or undefined
 | ||||
|             assertParamExists('removeAssetFromAlbum', 'removeAssetsDto', removeAssetsDto) | ||||
|             // verify required parameter 'bulkIdsDto' is not null or undefined
 | ||||
|             assertParamExists('removeAssetFromAlbum', 'bulkIdsDto', bulkIdsDto) | ||||
|             const localVarPath = `/album/{id}/assets` | ||||
|                 .replace(`{${"id"}}`, encodeURIComponent(String(id))); | ||||
|             // use dummy base URL string because the URL constructor only accepts absolute URLs.
 | ||||
| @@ -4037,7 +3999,7 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration | ||||
|             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||
|             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||
|             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||
|             localVarRequestOptions.data = serializeDataIfNeeded(removeAssetsDto, localVarRequestOptions, configuration) | ||||
|             localVarRequestOptions.data = serializeDataIfNeeded(bulkIdsDto, localVarRequestOptions, configuration) | ||||
| 
 | ||||
|             return { | ||||
|                 url: toPathString(localVarUrlObj), | ||||
| @@ -4151,13 +4113,13 @@ export const AlbumApiFp = function(configuration?: Configuration) { | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} id  | ||||
|          * @param {AddAssetsDto} addAssetsDto  | ||||
|          * @param {BulkIdsDto} bulkIdsDto  | ||||
|          * @param {string} [key]  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         async addAssetsToAlbum(id: string, addAssetsDto: AddAssetsDto, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AddAssetsResponseDto>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.addAssetsToAlbum(id, addAssetsDto, key, options); | ||||
|         async addAssetsToAlbum(id: string, bulkIdsDto: BulkIdsDto, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<BulkIdResponseDto>>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.addAssetsToAlbum(id, bulkIdsDto, key, options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
| @@ -4225,12 +4187,12 @@ export const AlbumApiFp = function(configuration?: Configuration) { | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} id  | ||||
|          * @param {RemoveAssetsDto} removeAssetsDto  | ||||
|          * @param {BulkIdsDto} bulkIdsDto  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         async removeAssetFromAlbum(id: string, removeAssetsDto: RemoveAssetsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AlbumResponseDto>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.removeAssetFromAlbum(id, removeAssetsDto, options); | ||||
|         async removeAssetFromAlbum(id: string, bulkIdsDto: BulkIdsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<BulkIdResponseDto>>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.removeAssetFromAlbum(id, bulkIdsDto, options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
| @@ -4268,13 +4230,13 @@ export const AlbumApiFactory = function (configuration?: Configuration, basePath | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} id  | ||||
|          * @param {AddAssetsDto} addAssetsDto  | ||||
|          * @param {BulkIdsDto} bulkIdsDto  | ||||
|          * @param {string} [key]  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         addAssetsToAlbum(id: string, addAssetsDto: AddAssetsDto, key?: string, options?: any): AxiosPromise<AddAssetsResponseDto> { | ||||
|             return localVarFp.addAssetsToAlbum(id, addAssetsDto, key, options).then((request) => request(axios, basePath)); | ||||
|         addAssetsToAlbum(id: string, bulkIdsDto: BulkIdsDto, key?: string, options?: any): AxiosPromise<Array<BulkIdResponseDto>> { | ||||
|             return localVarFp.addAssetsToAlbum(id, bulkIdsDto, key, options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
| @@ -4335,12 +4297,12 @@ export const AlbumApiFactory = function (configuration?: Configuration, basePath | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} id  | ||||
|          * @param {RemoveAssetsDto} removeAssetsDto  | ||||
|          * @param {BulkIdsDto} bulkIdsDto  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         removeAssetFromAlbum(id: string, removeAssetsDto: RemoveAssetsDto, options?: any): AxiosPromise<AlbumResponseDto> { | ||||
|             return localVarFp.removeAssetFromAlbum(id, removeAssetsDto, options).then((request) => request(axios, basePath)); | ||||
|         removeAssetFromAlbum(id: string, bulkIdsDto: BulkIdsDto, options?: any): AxiosPromise<Array<BulkIdResponseDto>> { | ||||
|             return localVarFp.removeAssetFromAlbum(id, bulkIdsDto, options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
| @@ -4380,10 +4342,10 @@ export interface AlbumApiAddAssetsToAlbumRequest { | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      * @type {AddAssetsDto} | ||||
|      * @type {BulkIdsDto} | ||||
|      * @memberof AlbumApiAddAssetsToAlbum | ||||
|      */ | ||||
|     readonly addAssetsDto: AddAssetsDto | ||||
|     readonly bulkIdsDto: BulkIdsDto | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
| @@ -4499,10 +4461,10 @@ export interface AlbumApiRemoveAssetFromAlbumRequest { | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      * @type {RemoveAssetsDto} | ||||
|      * @type {BulkIdsDto} | ||||
|      * @memberof AlbumApiRemoveAssetFromAlbum | ||||
|      */ | ||||
|     readonly removeAssetsDto: RemoveAssetsDto | ||||
|     readonly bulkIdsDto: BulkIdsDto | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @@ -4562,7 +4524,7 @@ export class AlbumApi extends BaseAPI { | ||||
|      * @memberof AlbumApi | ||||
|      */ | ||||
|     public addAssetsToAlbum(requestParameters: AlbumApiAddAssetsToAlbumRequest, options?: AxiosRequestConfig) { | ||||
|         return AlbumApiFp(this.configuration).addAssetsToAlbum(requestParameters.id, requestParameters.addAssetsDto, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); | ||||
|         return AlbumApiFp(this.configuration).addAssetsToAlbum(requestParameters.id, requestParameters.bulkIdsDto, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -4638,7 +4600,7 @@ export class AlbumApi extends BaseAPI { | ||||
|      * @memberof AlbumApi | ||||
|      */ | ||||
|     public removeAssetFromAlbum(requestParameters: AlbumApiRemoveAssetFromAlbumRequest, options?: AxiosRequestConfig) { | ||||
|         return AlbumApiFp(this.configuration).removeAssetFromAlbum(requestParameters.id, requestParameters.removeAssetsDto, options).then((request) => request(this.axios, this.basePath)); | ||||
|         return AlbumApiFp(this.configuration).removeAssetFromAlbum(requestParameters.id, requestParameters.bulkIdsDto, options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|   | ||||
| @@ -92,6 +92,7 @@ | ||||
|  | ||||
|   let multiSelectAsset: Set<AssetResponseDto> = new Set(); | ||||
|   $: isMultiSelectionMode = multiSelectAsset.size > 0; | ||||
|   $: isMultiSelectionUserOwned = Array.from(multiSelectAsset).every((asset) => asset.ownerId === currentUser?.id); | ||||
|  | ||||
|   afterNavigate(({ from }) => { | ||||
|     backUrl = from?.url.pathname ?? '/albums'; | ||||
| @@ -182,24 +183,24 @@ | ||||
|   const createAlbumHandler = async (event: CustomEvent) => { | ||||
|     const { assets }: { assets: AssetResponseDto[] } = event.detail; | ||||
|     try { | ||||
|       const { data } = await api.albumApi.addAssetsToAlbum({ | ||||
|       const { data: results } = await api.albumApi.addAssetsToAlbum({ | ||||
|         id: album.id, | ||||
|         addAssetsDto: { | ||||
|           assetIds: assets.map((a) => a.id), | ||||
|         }, | ||||
|         bulkIdsDto: { ids: assets.map((a) => a.id) }, | ||||
|         key: sharedLink?.key, | ||||
|       }); | ||||
|  | ||||
|       if (data.album) { | ||||
|         album = data.album; | ||||
|       } | ||||
|       const count = results.filter(({ success }) => success).length; | ||||
|       notificationController.show({ | ||||
|         type: NotificationType.Info, | ||||
|         message: `Added ${count} asset${count === 1 ? '' : 's'}`, | ||||
|       }); | ||||
|  | ||||
|       const { data } = await api.albumApi.getAlbumInfo({ id: album.id }); | ||||
|       album = data; | ||||
|  | ||||
|       isShowAssetSelection = false; | ||||
|     } catch (e) { | ||||
|       console.error('Error [createAlbumHandler] ', e); | ||||
|       notificationController.show({ | ||||
|         type: NotificationType.Error, | ||||
|         message: 'Error creating album, check console for more details', | ||||
|       }); | ||||
|       handleError(e, 'Error creating album'); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
| @@ -307,7 +308,7 @@ | ||||
|       {#if sharedLink?.allowDownload || !isPublicShared} | ||||
|         <DownloadAction filename="{album.albumName}.zip" sharedLinkKey={sharedLink?.key} /> | ||||
|       {/if} | ||||
|       {#if isOwned} | ||||
|       {#if isOwned || isMultiSelectionUserOwned} | ||||
|         <RemoveFromAlbum bind:album /> | ||||
|       {/if} | ||||
|     </AssetSelectControlBar> | ||||
|   | ||||
| @@ -189,11 +189,8 @@ | ||||
|     isShowAlbumPicker = false; | ||||
|     const album = event.detail.album; | ||||
|  | ||||
|     addAssetsToAlbum(album.id, [asset.id]).then((dto) => { | ||||
|       if (dto.successfullyAdded === 1 && dto.album) { | ||||
|         appearsInAlbums = [...appearsInAlbums, dto.album]; | ||||
|       } | ||||
|     }); | ||||
|     await addAssetsToAlbum(album.id, [asset.id]); | ||||
|     await getAllAlbums(); | ||||
|   }; | ||||
|  | ||||
|   const disableKeyDownEvent = () => { | ||||
|   | ||||
| @@ -44,10 +44,9 @@ | ||||
|   const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => { | ||||
|     showAlbumPicker = false; | ||||
|     const album = event.detail.album; | ||||
|  | ||||
|     const assetIds = Array.from(getAssets()).map((asset) => asset.id); | ||||
|  | ||||
|     addAssetsToAlbum(album.id, assetIds).then(clearSelect); | ||||
|     await addAssetsToAlbum(album.id, assetIds); | ||||
|     clearSelect(); | ||||
|   }; | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -17,14 +17,20 @@ | ||||
|  | ||||
|   const removeFromAlbum = async () => { | ||||
|     try { | ||||
|       const { data } = await api.albumApi.removeAssetFromAlbum({ | ||||
|       const { data: results } = await api.albumApi.removeAssetFromAlbum({ | ||||
|         id: album.id, | ||||
|         removeAssetsDto: { | ||||
|           assetIds: Array.from(getAssets()).map((a) => a.id), | ||||
|         }, | ||||
|         bulkIdsDto: { ids: Array.from(getAssets()).map((a) => a.id) }, | ||||
|       }); | ||||
|  | ||||
|       const { data } = await api.albumApi.getAlbumInfo({ id: album.id }); | ||||
|       album = data; | ||||
|  | ||||
|       const count = results.filter(({ success }) => success).length; | ||||
|       notificationController.show({ | ||||
|         type: NotificationType.Info, | ||||
|         message: `Removed ${count} asset${count === 1 ? '' : 's'}`, | ||||
|       }); | ||||
|  | ||||
|       clearSelect(); | ||||
|     } catch (e) { | ||||
|       console.error('Error [album-viewer] [removeAssetFromAlbum]', e); | ||||
|   | ||||
| @@ -1,23 +1,24 @@ | ||||
| import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification'; | ||||
| import { downloadManager } from '$lib/stores/download'; | ||||
| import { AddAssetsResponseDto, api, AssetApiGetDownloadInfoRequest, AssetResponseDto, DownloadResponseDto } from '@api'; | ||||
| import { api, AssetApiGetDownloadInfoRequest, BulkIdResponseDto, AssetResponseDto, DownloadResponseDto } from '@api'; | ||||
| import { handleError } from './handle-error'; | ||||
|  | ||||
| export const addAssetsToAlbum = async ( | ||||
|   albumId: string, | ||||
|   assetIds: Array<string>, | ||||
|   key: string | undefined = undefined, | ||||
| ): Promise<AddAssetsResponseDto> => | ||||
|   api.albumApi.addAssetsToAlbum({ id: albumId, addAssetsDto: { assetIds }, key }).then(({ data: dto }) => { | ||||
|     if (dto.successfullyAdded > 0) { | ||||
| ): Promise<BulkIdResponseDto[]> => | ||||
|   api.albumApi.addAssetsToAlbum({ id: albumId, bulkIdsDto: { ids: assetIds }, key }).then(({ data: results }) => { | ||||
|     const count = results.filter(({ success }) => success).length; | ||||
|     if (count > 0) { | ||||
|       // This might be 0 if the user tries to add an asset that is already in the album | ||||
|       notificationController.show({ | ||||
|         message: `Added ${dto.successfullyAdded} to ${dto.album?.albumName}`, | ||||
|         type: NotificationType.Info, | ||||
|         message: `Added ${count} asset${count === 1 ? '' : 's'}`, | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     return dto; | ||||
|     return results; | ||||
|   }); | ||||
|  | ||||
| const downloadBlob = (data: Blob, filename: string) => { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user