mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	fix(web,server): album share performance (#3698)
This commit is contained in:
		| @@ -7,8 +7,12 @@ export interface AlbumAssetCount { | ||||
|   assetCount: number; | ||||
| } | ||||
|  | ||||
| export interface AlbumInfoOptions { | ||||
|   withAssets: boolean; | ||||
| } | ||||
|  | ||||
| export interface IAlbumRepository { | ||||
|   getById(id: string): Promise<AlbumEntity | null>; | ||||
|   getById(id: string, options: AlbumInfoOptions): Promise<AlbumEntity | null>; | ||||
|   getByIds(ids: string[]): Promise<AlbumEntity[]>; | ||||
|   getByAssetId(ownerId: string, assetId: string): Promise<AlbumEntity[]>; | ||||
|   hasAsset(id: string, assetId: string): Promise<boolean>; | ||||
|   | ||||
| @@ -364,6 +364,7 @@ describe(AlbumService.name, () => { | ||||
|         updatedAt: expect.any(Date), | ||||
|         sharedUsers: [], | ||||
|       }); | ||||
|       expect(albumMock.getById).toHaveBeenCalledWith(albumStub.sharedWithUser.id, { withAssets: false }); | ||||
|     }); | ||||
|  | ||||
|     it('should prevent removing a shared user from a not-owned album (shared with auth user)', async () => { | ||||
| @@ -432,7 +433,7 @@ describe(AlbumService.name, () => { | ||||
|  | ||||
|       await sut.get(authStub.admin, albumStub.oneAsset.id, {}); | ||||
|  | ||||
|       expect(albumMock.getById).toHaveBeenCalledWith(albumStub.oneAsset.id); | ||||
|       expect(albumMock.getById).toHaveBeenCalledWith(albumStub.oneAsset.id, { withAssets: true }); | ||||
|       expect(accessMock.album.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, albumStub.oneAsset.id); | ||||
|     }); | ||||
|  | ||||
| @@ -442,7 +443,7 @@ describe(AlbumService.name, () => { | ||||
|  | ||||
|       await sut.get(authStub.adminSharedLink, 'album-123', {}); | ||||
|  | ||||
|       expect(albumMock.getById).toHaveBeenCalledWith('album-123'); | ||||
|       expect(albumMock.getById).toHaveBeenCalledWith('album-123', { withAssets: true }); | ||||
|       expect(accessMock.album.hasSharedLinkAccess).toHaveBeenCalledWith( | ||||
|         authStub.adminSharedLink.sharedLinkId, | ||||
|         'album-123', | ||||
| @@ -455,7 +456,7 @@ describe(AlbumService.name, () => { | ||||
|  | ||||
|       await sut.get(authStub.user1, 'album-123', {}); | ||||
|  | ||||
|       expect(albumMock.getById).toHaveBeenCalledWith('album-123'); | ||||
|       expect(albumMock.getById).toHaveBeenCalledWith('album-123', { withAssets: true }); | ||||
|       expect(accessMock.album.hasSharedAlbumAccess).toHaveBeenCalledWith(authStub.user1.id, 'album-123'); | ||||
|     }); | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,7 @@ import { | ||||
|   mapAlbumWithAssets, | ||||
|   mapAlbumWithoutAssets, | ||||
| } from './album-response.dto'; | ||||
| import { IAlbumRepository } from './album.repository'; | ||||
| import { AlbumInfoOptions, IAlbumRepository } from './album.repository'; | ||||
| import { AddUsersDto, AlbumInfoDto, CreateAlbumDto, GetAlbumsDto, UpdateAlbumDto } from './dto'; | ||||
|  | ||||
| @Injectable() | ||||
| @@ -84,7 +84,7 @@ export class AlbumService { | ||||
|   async get(authUser: AuthUserDto, id: string, dto: AlbumInfoDto) { | ||||
|     await this.access.requirePermission(authUser, Permission.ALBUM_READ, id); | ||||
|     await this.albumRepository.updateThumbnails(); | ||||
|     return mapAlbum(await this.findOrFail(id), !dto.withoutAssets); | ||||
|     return mapAlbum(await this.findOrFail(id, { withAssets: true }), !dto.withoutAssets); | ||||
|   } | ||||
|  | ||||
|   async create(authUser: AuthUserDto, dto: CreateAlbumDto): Promise<AlbumResponseDto> { | ||||
| @@ -111,7 +111,7 @@ export class AlbumService { | ||||
|   async update(authUser: AuthUserDto, id: string, dto: UpdateAlbumDto): Promise<AlbumResponseDto> { | ||||
|     await this.access.requirePermission(authUser, Permission.ALBUM_UPDATE, id); | ||||
|  | ||||
|     const album = await this.findOrFail(id); | ||||
|     const album = await this.findOrFail(id, { withAssets: true }); | ||||
|  | ||||
|     if (dto.albumThumbnailAssetId) { | ||||
|       const valid = await this.albumRepository.hasAsset(id, dto.albumThumbnailAssetId); | ||||
| @@ -129,13 +129,13 @@ export class AlbumService { | ||||
|  | ||||
|     await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ALBUM, data: { ids: [updatedAlbum.id] } }); | ||||
|  | ||||
|     return mapAlbumWithAssets(updatedAlbum); | ||||
|     return mapAlbumWithoutAssets(updatedAlbum); | ||||
|   } | ||||
|  | ||||
|   async delete(authUser: AuthUserDto, id: string): Promise<void> { | ||||
|     await this.access.requirePermission(authUser, Permission.ALBUM_DELETE, id); | ||||
|  | ||||
|     const album = await this.albumRepository.getById(id); | ||||
|     const album = await this.findOrFail(id, { withAssets: false }); | ||||
|     if (!album) { | ||||
|       throw new BadRequestException('Album not found'); | ||||
|     } | ||||
| @@ -145,7 +145,7 @@ export class AlbumService { | ||||
|   } | ||||
|  | ||||
|   async addAssets(authUser: AuthUserDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> { | ||||
|     const album = await this.findOrFail(id); | ||||
|     const album = await this.findOrFail(id, { withAssets: true }); | ||||
|  | ||||
|     await this.access.requirePermission(authUser, Permission.ALBUM_READ, id); | ||||
|  | ||||
| @@ -181,7 +181,7 @@ export class AlbumService { | ||||
|   } | ||||
|  | ||||
|   async removeAssets(authUser: AuthUserDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> { | ||||
|     const album = await this.findOrFail(id); | ||||
|     const album = await this.findOrFail(id, { withAssets: true }); | ||||
|  | ||||
|     await this.access.requirePermission(authUser, Permission.ALBUM_READ, id); | ||||
|  | ||||
| @@ -225,7 +225,7 @@ export class AlbumService { | ||||
|   async addUsers(authUser: AuthUserDto, id: string, dto: AddUsersDto): Promise<AlbumResponseDto> { | ||||
|     await this.access.requirePermission(authUser, Permission.ALBUM_SHARE, id); | ||||
|  | ||||
|     const album = await this.findOrFail(id); | ||||
|     const album = await this.findOrFail(id, { withAssets: false }); | ||||
|  | ||||
|     for (const userId of dto.sharedUserIds) { | ||||
|       const exists = album.sharedUsers.find((user) => user.id === userId); | ||||
| @@ -247,7 +247,7 @@ export class AlbumService { | ||||
|         updatedAt: new Date(), | ||||
|         sharedUsers: album.sharedUsers, | ||||
|       }) | ||||
|       .then(mapAlbumWithAssets); | ||||
|       .then(mapAlbumWithoutAssets); | ||||
|   } | ||||
|  | ||||
|   async removeUser(authUser: AuthUserDto, id: string, userId: string | 'me'): Promise<void> { | ||||
| @@ -255,7 +255,7 @@ export class AlbumService { | ||||
|       userId = authUser.id; | ||||
|     } | ||||
|  | ||||
|     const album = await this.findOrFail(id); | ||||
|     const album = await this.findOrFail(id, { withAssets: false }); | ||||
|  | ||||
|     if (album.ownerId === userId) { | ||||
|       throw new BadRequestException('Cannot remove album owner'); | ||||
| @@ -278,8 +278,8 @@ export class AlbumService { | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   private async findOrFail(id: string) { | ||||
|     const album = await this.albumRepository.getById(id); | ||||
|   private async findOrFail(id: string, options: AlbumInfoOptions) { | ||||
|     const album = await this.albumRepository.getById(id, options); | ||||
|     if (!album) { | ||||
|       throw new BadRequestException('Album not found'); | ||||
|     } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { AlbumAssetCount, IAlbumRepository } from '@app/domain'; | ||||
| import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from '@app/domain'; | ||||
| import { Injectable } from '@nestjs/common'; | ||||
| import { InjectRepository } from '@nestjs/typeorm'; | ||||
| import { In, IsNull, Not, Repository } from 'typeorm'; | ||||
| import { FindOptionsOrder, FindOptionsRelations, In, IsNull, Not, Repository } from 'typeorm'; | ||||
| import { dataSource } from '../database.config'; | ||||
| import { AlbumEntity, AssetEntity } from '../entities'; | ||||
|  | ||||
| @@ -12,25 +12,27 @@ export class AlbumRepository implements IAlbumRepository { | ||||
|     @InjectRepository(AlbumEntity) private repository: Repository<AlbumEntity>, | ||||
|   ) {} | ||||
|  | ||||
|   getById(id: string): Promise<AlbumEntity | null> { | ||||
|     return this.repository.findOne({ | ||||
|       where: { | ||||
|         id, | ||||
|       }, | ||||
|       relations: { | ||||
|         owner: true, | ||||
|         sharedUsers: true, | ||||
|         assets: { | ||||
|           exifInfo: true, | ||||
|         }, | ||||
|         sharedLinks: true, | ||||
|       }, | ||||
|       order: { | ||||
|         assets: { | ||||
|           fileCreatedAt: 'DESC', | ||||
|         }, | ||||
|       }, | ||||
|     }); | ||||
|   getById(id: string, options: AlbumInfoOptions): Promise<AlbumEntity | null> { | ||||
|     const relations: FindOptionsRelations<AlbumEntity> = { | ||||
|       owner: true, | ||||
|       sharedUsers: true, | ||||
|       assets: false, | ||||
|       sharedLinks: true, | ||||
|     }; | ||||
|  | ||||
|     const order: FindOptionsOrder<AlbumEntity> = {}; | ||||
|  | ||||
|     if (options.withAssets) { | ||||
|       relations.assets = { | ||||
|         exifInfo: true, | ||||
|       }; | ||||
|  | ||||
|       order.assets = { | ||||
|         fileCreatedAt: 'DESC', | ||||
|       }; | ||||
|     } | ||||
|  | ||||
|     return this.repository.findOne({ where: { id }, relations, order }); | ||||
|   } | ||||
|  | ||||
|   getByIds(ids: string[]): Promise<AlbumEntity[]> { | ||||
|   | ||||
| @@ -100,7 +100,7 @@ | ||||
|   }); | ||||
|  | ||||
|   const refreshAlbum = async () => { | ||||
|     const { data } = await api.albumApi.getAlbumInfo({ id: album.id, withoutAssets: false }); | ||||
|     const { data } = await api.albumApi.getAlbumInfo({ id: album.id, withoutAssets: true }); | ||||
|     album = data; | ||||
|   }; | ||||
|  | ||||
| @@ -261,9 +261,9 @@ | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const handleUpdateDescription = (description: string) => { | ||||
|   const handleUpdateDescription = async (description: string) => { | ||||
|     try { | ||||
|       api.albumApi.updateAlbumInfo({ | ||||
|       await api.albumApi.updateAlbumInfo({ | ||||
|         id: album.id, | ||||
|         updateAlbumDto: { | ||||
|           description, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user