0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-07 00:50:23 -05:00

fix(web,server): album share performance (#3698)

This commit is contained in:
Jason Rasmussen 2023-08-15 14:34:02 -04:00 committed by GitHub
parent af1f00dff9
commit 0abbd85134
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 47 additions and 40 deletions

View file

@ -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>;

View file

@ -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');
});

View file

@ -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');
}

View file

@ -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[]> {

View file

@ -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,