mirror of
https://github.com/zebrajr/immich.git
synced 2025-12-06 12:20:54 +01:00
chore(server): Improve add to multiple albums via bulk checks and inserts (#21052)
* - add addAssetIdsToAlbums to album repo - update albumService to determine all albums and assets with access and coalesce into one set of album_assets to insert * - remove hasAsset check (unnecessary) * - lint * - cleanup * - remove success counts from addAssetsToAlbums results - Fix tests * open-api * await album update
This commit is contained in:
parent
28dce2d0df
commit
3f1e11afcc
Binary file not shown.
|
|
@ -10007,12 +10007,6 @@
|
||||||
},
|
},
|
||||||
"AlbumsAddAssetsResponseDto": {
|
"AlbumsAddAssetsResponseDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"albumSuccessCount": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"assetSuccessCount": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"error": {
|
"error": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
|
|
@ -10025,8 +10019,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"albumSuccessCount",
|
|
||||||
"assetSuccessCount",
|
|
||||||
"success"
|
"success"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|
|
||||||
|
|
@ -389,8 +389,6 @@ export type AlbumsAddAssetsDto = {
|
||||||
assetIds: string[];
|
assetIds: string[];
|
||||||
};
|
};
|
||||||
export type AlbumsAddAssetsResponseDto = {
|
export type AlbumsAddAssetsResponseDto = {
|
||||||
albumSuccessCount: number;
|
|
||||||
assetSuccessCount: number;
|
|
||||||
error?: BulkIdErrorReason;
|
error?: BulkIdErrorReason;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -65,10 +65,6 @@ export class AlbumsAddAssetsDto {
|
||||||
|
|
||||||
export class AlbumsAddAssetsResponseDto {
|
export class AlbumsAddAssetsResponseDto {
|
||||||
success!: boolean;
|
success!: boolean;
|
||||||
@ApiProperty({ type: 'integer' })
|
|
||||||
albumSuccessCount!: number;
|
|
||||||
@ApiProperty({ type: 'integer' })
|
|
||||||
assetSuccessCount!: number;
|
|
||||||
@ValidateEnum({ enum: BulkIdErrorReason, name: 'BulkIdErrorReason', optional: true })
|
@ValidateEnum({ enum: BulkIdErrorReason, name: 'BulkIdErrorReason', optional: true })
|
||||||
error?: BulkIdErrorReason;
|
error?: BulkIdErrorReason;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -321,6 +321,14 @@ export class AlbumRepository {
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Chunked({ chunkSize: 30_000 })
|
||||||
|
async addAssetIdsToAlbums(values: { albumsId: string; assetsId: string }[]): Promise<void> {
|
||||||
|
if (values.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.db.insertInto('album_asset').values(values).execute();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes sure all thumbnails for albums are updated by:
|
* Makes sure all thumbnails for albums are updated by:
|
||||||
* - Removing thumbnails from albums without assets
|
* - Removing thumbnails from albums without assets
|
||||||
|
|
|
||||||
|
|
@ -778,9 +778,7 @@ describe(AlbumService.name, () => {
|
||||||
|
|
||||||
describe('addAssetsToAlbums', () => {
|
describe('addAssetsToAlbums', () => {
|
||||||
it('should allow the owner to add assets', async () => {
|
it('should allow the owner to add assets', async () => {
|
||||||
mocks.access.album.checkOwnerAccess
|
mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set(['album-123', 'album-321']));
|
||||||
.mockResolvedValueOnce(new Set(['album-123']))
|
|
||||||
.mockResolvedValueOnce(new Set(['album-321']));
|
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||||
mocks.album.getById
|
mocks.album.getById
|
||||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.empty))
|
.mockResolvedValueOnce(_.cloneDeep(albumStub.empty))
|
||||||
|
|
@ -792,7 +790,7 @@ describe(AlbumService.name, () => {
|
||||||
albumIds: ['album-123', 'album-321'],
|
albumIds: ['album-123', 'album-321'],
|
||||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({ success: true, albumSuccessCount: 2, assetSuccessCount: 3 });
|
).resolves.toEqual({ success: true, error: undefined });
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
||||||
|
|
@ -805,14 +803,18 @@ describe(AlbumService.name, () => {
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: 'asset-1',
|
albumThumbnailAssetId: 'asset-1',
|
||||||
});
|
});
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-321', ['asset-1', 'asset-2', 'asset-3']);
|
{ albumsId: 'album-123', assetsId: 'asset-1' },
|
||||||
|
{ albumsId: 'album-123', assetsId: 'asset-2' },
|
||||||
|
{ albumsId: 'album-123', assetsId: 'asset-3' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-1' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-2' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-3' },
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not set the thumbnail if the album has one already', async () => {
|
it('should not set the thumbnail if the album has one already', async () => {
|
||||||
mocks.access.album.checkOwnerAccess
|
mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set(['album-123', 'album-321']));
|
||||||
.mockResolvedValueOnce(new Set(['album-123']))
|
|
||||||
.mockResolvedValueOnce(new Set(['album-321']));
|
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||||
mocks.album.getById
|
mocks.album.getById
|
||||||
.mockResolvedValueOnce(_.cloneDeep({ ...albumStub.empty, albumThumbnailAssetId: 'asset-id' }))
|
.mockResolvedValueOnce(_.cloneDeep({ ...albumStub.empty, albumThumbnailAssetId: 'asset-id' }))
|
||||||
|
|
@ -824,7 +826,7 @@ describe(AlbumService.name, () => {
|
||||||
albumIds: ['album-123', 'album-321'],
|
albumIds: ['album-123', 'album-321'],
|
||||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({ success: true, albumSuccessCount: 2, assetSuccessCount: 3 });
|
).resolves.toEqual({ success: true, error: undefined });
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
||||||
|
|
@ -837,14 +839,18 @@ describe(AlbumService.name, () => {
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: 'asset-id',
|
albumThumbnailAssetId: 'asset-id',
|
||||||
});
|
});
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-321', ['asset-1', 'asset-2', 'asset-3']);
|
{ albumsId: 'album-123', assetsId: 'asset-1' },
|
||||||
|
{ albumsId: 'album-123', assetsId: 'asset-2' },
|
||||||
|
{ albumsId: 'album-123', assetsId: 'asset-3' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-1' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-2' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-3' },
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow a shared user to add assets', async () => {
|
it('should allow a shared user to add assets', async () => {
|
||||||
mocks.access.album.checkSharedAlbumAccess
|
mocks.access.album.checkSharedAlbumAccess.mockResolvedValueOnce(new Set(['album-123', 'album-321']));
|
||||||
.mockResolvedValueOnce(new Set(['album-123']))
|
|
||||||
.mockResolvedValueOnce(new Set(['album-321']));
|
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||||
mocks.album.getById
|
mocks.album.getById
|
||||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithUser))
|
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithUser))
|
||||||
|
|
@ -856,7 +862,7 @@ describe(AlbumService.name, () => {
|
||||||
albumIds: ['album-123', 'album-321'],
|
albumIds: ['album-123', 'album-321'],
|
||||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({ success: true, albumSuccessCount: 2, assetSuccessCount: 3 });
|
).resolves.toEqual({ success: true, error: undefined });
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
||||||
|
|
@ -869,8 +875,14 @@ describe(AlbumService.name, () => {
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: 'asset-1',
|
albumThumbnailAssetId: 'asset-1',
|
||||||
});
|
});
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-321', ['asset-1', 'asset-2', 'asset-3']);
|
{ albumsId: 'album-123', assetsId: 'asset-1' },
|
||||||
|
{ albumsId: 'album-123', assetsId: 'asset-2' },
|
||||||
|
{ albumsId: 'album-123', assetsId: 'asset-3' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-1' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-2' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-3' },
|
||||||
|
]);
|
||||||
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
|
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
|
||||||
id: 'album-123',
|
id: 'album-123',
|
||||||
recipientId: 'admin_id',
|
recipientId: 'admin_id',
|
||||||
|
|
@ -896,18 +908,14 @@ describe(AlbumService.name, () => {
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({
|
).resolves.toEqual({
|
||||||
success: false,
|
success: false,
|
||||||
albumSuccessCount: 0,
|
error: BulkIdErrorReason.NO_PERMISSION,
|
||||||
assetSuccessCount: 0,
|
|
||||||
error: BulkIdErrorReason.UNKNOWN,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow a shared link user to add assets to multiple albums', async () => {
|
it('should not allow a shared link user to add assets to multiple albums', async () => {
|
||||||
mocks.access.album.checkSharedLinkAccess
|
mocks.access.album.checkSharedLinkAccess.mockResolvedValueOnce(new Set(['album-123']));
|
||||||
.mockResolvedValueOnce(new Set(['album-123']))
|
|
||||||
.mockResolvedValueOnce(new Set());
|
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||||
mocks.album.getById
|
mocks.album.getById
|
||||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithUser))
|
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithUser))
|
||||||
|
|
@ -919,7 +927,7 @@ describe(AlbumService.name, () => {
|
||||||
albumIds: ['album-123', 'album-321'],
|
albumIds: ['album-123', 'album-321'],
|
||||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({ success: true, albumSuccessCount: 1, assetSuccessCount: 3 });
|
).resolves.toEqual({ success: true, error: undefined });
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledTimes(1);
|
expect(mocks.album.update).toHaveBeenCalledTimes(1);
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
||||||
|
|
@ -927,22 +935,23 @@ describe(AlbumService.name, () => {
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: 'asset-1',
|
albumThumbnailAssetId: 'asset-1',
|
||||||
});
|
});
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledTimes(1);
|
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
{ albumsId: 'album-123', assetsId: 'asset-1' },
|
||||||
|
{ albumsId: 'album-123', assetsId: 'asset-2' },
|
||||||
|
{ albumsId: 'album-123', assetsId: 'asset-3' },
|
||||||
|
]);
|
||||||
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
|
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
|
||||||
id: 'album-123',
|
id: 'album-123',
|
||||||
recipientId: 'user-id',
|
recipientId: 'user-id',
|
||||||
});
|
});
|
||||||
expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalledWith(
|
expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalledWith(
|
||||||
authStub.adminSharedLink.sharedLink?.id,
|
authStub.adminSharedLink.sharedLink?.id,
|
||||||
new Set(['album-123']),
|
new Set(['album-123', 'album-321']),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow adding assets shared via partner sharing', async () => {
|
it('should allow adding assets shared via partner sharing', async () => {
|
||||||
mocks.access.album.checkOwnerAccess
|
mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set(['album-123', 'album-321']));
|
||||||
.mockResolvedValueOnce(new Set(['album-123']))
|
|
||||||
.mockResolvedValueOnce(new Set(['album-321']));
|
|
||||||
mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||||
mocks.album.getById
|
mocks.album.getById
|
||||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.empty))
|
.mockResolvedValueOnce(_.cloneDeep(albumStub.empty))
|
||||||
|
|
@ -954,7 +963,7 @@ describe(AlbumService.name, () => {
|
||||||
albumIds: ['album-123', 'album-321'],
|
albumIds: ['album-123', 'album-321'],
|
||||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({ success: true, albumSuccessCount: 2, assetSuccessCount: 3 });
|
).resolves.toEqual({ success: true, error: undefined });
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
||||||
|
|
@ -967,8 +976,14 @@ describe(AlbumService.name, () => {
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: 'asset-1',
|
albumThumbnailAssetId: 'asset-1',
|
||||||
});
|
});
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-321', ['asset-1', 'asset-2', 'asset-3']);
|
{ albumsId: 'album-123', assetsId: 'asset-1' },
|
||||||
|
{ albumsId: 'album-123', assetsId: 'asset-2' },
|
||||||
|
{ albumsId: 'album-123', assetsId: 'asset-3' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-1' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-2' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-3' },
|
||||||
|
]);
|
||||||
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(
|
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(
|
||||||
authStub.admin.user.id,
|
authStub.admin.user.id,
|
||||||
new Set(['asset-1', 'asset-2', 'asset-3']),
|
new Set(['asset-1', 'asset-2', 'asset-3']),
|
||||||
|
|
@ -976,23 +991,21 @@ describe(AlbumService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip some duplicate assets', async () => {
|
it('should skip some duplicate assets', async () => {
|
||||||
mocks.access.album.checkOwnerAccess
|
mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set(['album-123', 'album-321']));
|
||||||
.mockResolvedValueOnce(new Set(['album-123']))
|
|
||||||
.mockResolvedValueOnce(new Set(['album-321']));
|
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||||
mocks.album.getById
|
|
||||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.empty))
|
|
||||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.oneAsset));
|
|
||||||
mocks.album.getAssetIds
|
mocks.album.getAssetIds
|
||||||
.mockResolvedValueOnce(new Set(['asset-1', 'asset-2', 'asset-3']))
|
.mockResolvedValueOnce(new Set(['asset-1', 'asset-2', 'asset-3']))
|
||||||
.mockResolvedValueOnce(new Set());
|
.mockResolvedValueOnce(new Set());
|
||||||
|
mocks.album.getById
|
||||||
|
.mockResolvedValueOnce(_.cloneDeep(albumStub.empty))
|
||||||
|
.mockResolvedValueOnce(_.cloneDeep(albumStub.oneAsset));
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.addAssetsToAlbums(authStub.admin, {
|
sut.addAssetsToAlbums(authStub.admin, {
|
||||||
albumIds: ['album-123', 'album-321'],
|
albumIds: ['album-123', 'album-321'],
|
||||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({ success: true, albumSuccessCount: 1, assetSuccessCount: 3 });
|
).resolves.toEqual({ success: true, error: undefined });
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledTimes(1);
|
expect(mocks.album.update).toHaveBeenCalledTimes(1);
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-321', {
|
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-321', {
|
||||||
|
|
@ -1000,8 +1013,11 @@ describe(AlbumService.name, () => {
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: 'asset-1',
|
albumThumbnailAssetId: 'asset-1',
|
||||||
});
|
});
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledTimes(1);
|
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-321', ['asset-1', 'asset-2', 'asset-3']);
|
{ albumsId: 'album-321', assetsId: 'asset-1' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-2' },
|
||||||
|
{ albumsId: 'album-321', assetsId: 'asset-3' },
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip all duplicate assets', async () => {
|
it('should skip all duplicate assets', async () => {
|
||||||
|
|
@ -1021,8 +1037,6 @@ describe(AlbumService.name, () => {
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({
|
).resolves.toEqual({
|
||||||
success: false,
|
success: false,
|
||||||
albumSuccessCount: 0,
|
|
||||||
assetSuccessCount: 0,
|
|
||||||
error: BulkIdErrorReason.DUPLICATE,
|
error: BulkIdErrorReason.DUPLICATE,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1046,9 +1060,7 @@ describe(AlbumService.name, () => {
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({
|
).resolves.toEqual({
|
||||||
success: false,
|
success: false,
|
||||||
albumSuccessCount: 0,
|
error: BulkIdErrorReason.NO_PERMISSION,
|
||||||
assetSuccessCount: 0,
|
|
||||||
error: BulkIdErrorReason.UNKNOWN,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||||
|
|
@ -1076,9 +1088,7 @@ describe(AlbumService.name, () => {
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({
|
).resolves.toEqual({
|
||||||
success: false,
|
success: false,
|
||||||
albumSuccessCount: 0,
|
error: BulkIdErrorReason.NO_PERMISSION,
|
||||||
assetSuccessCount: 0,
|
|
||||||
error: BulkIdErrorReason.UNKNOWN,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||||
|
|
@ -1099,9 +1109,7 @@ describe(AlbumService.name, () => {
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({
|
).resolves.toEqual({
|
||||||
success: false,
|
success: false,
|
||||||
albumSuccessCount: 0,
|
error: BulkIdErrorReason.NO_PERMISSION,
|
||||||
assetSuccessCount: 0,
|
|
||||||
error: BulkIdErrorReason.UNKNOWN,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalled();
|
expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalled();
|
||||||
|
|
|
||||||
|
|
@ -191,36 +191,57 @@ export class AlbumService extends BaseService {
|
||||||
async addAssetsToAlbums(auth: AuthDto, dto: AlbumsAddAssetsDto): Promise<AlbumsAddAssetsResponseDto> {
|
async addAssetsToAlbums(auth: AuthDto, dto: AlbumsAddAssetsDto): Promise<AlbumsAddAssetsResponseDto> {
|
||||||
const results: AlbumsAddAssetsResponseDto = {
|
const results: AlbumsAddAssetsResponseDto = {
|
||||||
success: false,
|
success: false,
|
||||||
albumSuccessCount: 0,
|
|
||||||
assetSuccessCount: 0,
|
|
||||||
error: BulkIdErrorReason.DUPLICATE,
|
error: BulkIdErrorReason.DUPLICATE,
|
||||||
};
|
};
|
||||||
const successfulAssetIds: Set<string> = new Set();
|
|
||||||
for (const albumId of dto.albumIds) {
|
|
||||||
try {
|
|
||||||
const albumResults = await this.addAssets(auth, albumId, { ids: dto.assetIds });
|
|
||||||
|
|
||||||
let success = false;
|
const allowedAlbumIds = await this.checkAccess({
|
||||||
for (const res of albumResults) {
|
auth,
|
||||||
if (res.success) {
|
permission: Permission.AlbumAssetCreate,
|
||||||
success = true;
|
ids: dto.albumIds,
|
||||||
results.success = true;
|
});
|
||||||
results.error = undefined;
|
if (allowedAlbumIds.size === 0) {
|
||||||
successfulAssetIds.add(res.id);
|
results.error = BulkIdErrorReason.NO_PERMISSION;
|
||||||
} else if (results.error && res.error !== BulkIdErrorReason.DUPLICATE) {
|
return results;
|
||||||
results.error = BulkIdErrorReason.UNKNOWN;
|
}
|
||||||
}
|
|
||||||
}
|
const allowedAssetIds = await this.checkAccess({ auth, permission: Permission.AssetShare, ids: dto.assetIds });
|
||||||
if (success) {
|
if (allowedAssetIds.size === 0) {
|
||||||
results.albumSuccessCount++;
|
results.error = BulkIdErrorReason.NO_PERMISSION;
|
||||||
}
|
return results;
|
||||||
} catch {
|
}
|
||||||
if (results.error) {
|
|
||||||
results.error = BulkIdErrorReason.UNKNOWN;
|
const albumAssetValues: { albumsId: string; assetsId: string }[] = [];
|
||||||
}
|
const events: { id: string; recipients: string[] }[] = [];
|
||||||
|
for (const albumId of allowedAlbumIds) {
|
||||||
|
const existingAssetIds = await this.albumRepository.getAssetIds(albumId, [...allowedAssetIds]);
|
||||||
|
const notPresentAssetIds = [...allowedAssetIds].filter((id) => !existingAssetIds.has(id));
|
||||||
|
if (notPresentAssetIds.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const album = await this.findOrFail(albumId, { withAssets: false });
|
||||||
|
results.error = undefined;
|
||||||
|
results.success = true;
|
||||||
|
|
||||||
|
for (const assetId of notPresentAssetIds) {
|
||||||
|
albumAssetValues.push({ albumsId: albumId, assetsId: assetId });
|
||||||
|
}
|
||||||
|
await this.albumRepository.update(albumId, {
|
||||||
|
id: albumId,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
albumThumbnailAssetId: album.albumThumbnailAssetId ?? notPresentAssetIds[0],
|
||||||
|
});
|
||||||
|
const allUsersExceptUs = [...album.albumUsers.map(({ user }) => user.id), album.owner.id].filter(
|
||||||
|
(userId) => userId !== auth.user.id,
|
||||||
|
);
|
||||||
|
events.push({ id: albumId, recipients: allUsersExceptUs });
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.albumRepository.addAssetIdsToAlbums(albumAssetValues);
|
||||||
|
for (const event of events) {
|
||||||
|
for (const recipientId of event.recipients) {
|
||||||
|
await this.eventRepository.emit('AlbumUpdate', { id: event.id, recipientId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
results.assetSuccessCount = successfulAssetIds.size;
|
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user