mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
feat(web): Add to Multiple Albums (#20072)
* Multi add to album picker: - update modal for multi select - Update add-to-album and add-to-album-action to work with new array return from AlbumPickerModal - Add asset-utils.addAssetsToAlbums (incomplete) * initial addToAlbums endpoint * - fix endpoint - add test * - update return type - make open-api * - simplify return dto - handle notification * - fix returns - clean up * - update i18n - format & check * - checks * - correct successId count - fix assets_cannot_be_added language call * tests * foromat * refactor * - update successful add message to included total attempted * - fix web test - format i18n * - fix open-api * - fix imports to resolve checks * - PR suggestions * open-api * refactor addAssetsToAlbums * refactor it again * - fix error returns and tests * - swap icon for IconButton - don't nest the buttons * open-api * - Cleanup multi-select button to match Thumbnail * merge and openapi * - remove onclick from icon element * - fix double onClose call with keyboard shortcuts * - spelling and formatting - apply new api permission * - open-api * chore: styling * translation --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
e00556a34a
commit
9ff664ed36
24 changed files with 1280 additions and 55 deletions
|
|
@ -65,6 +65,13 @@ describe(AlbumController.name, () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('PUT /albums/assets', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).put(`/albums/assets`);
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /albums/:id', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).patch(`/albums/${factory.uuid()}`).send({ albumName: 'New album name' });
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import {
|
|||
AddUsersDto,
|
||||
AlbumInfoDto,
|
||||
AlbumResponseDto,
|
||||
AlbumsAddAssetsDto,
|
||||
AlbumsAddAssetsResponseDto,
|
||||
AlbumStatisticsResponseDto,
|
||||
CreateAlbumDto,
|
||||
GetAlbumsDto,
|
||||
|
|
@ -77,6 +79,12 @@ export class AlbumController {
|
|||
return this.service.addAssets(auth, id, dto);
|
||||
}
|
||||
|
||||
@Put('assets')
|
||||
@Authenticated({ permission: Permission.AlbumAssetCreate, sharedLink: true })
|
||||
addAssetsToAlbums(@Auth() auth: AuthDto, @Body() dto: AlbumsAddAssetsDto): Promise<AlbumsAddAssetsResponseDto> {
|
||||
return this.service.addAssetsToAlbums(auth, dto);
|
||||
}
|
||||
|
||||
@Delete(':id/assets')
|
||||
@Authenticated({ permission: Permission.AlbumAssetDelete })
|
||||
removeAssetFromAlbum(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Type } from 'class-transformer';
|
|||
import { ArrayNotEmpty, IsArray, IsString, ValidateNested } from 'class-validator';
|
||||
import _ from 'lodash';
|
||||
import { AlbumUser, AuthSharedLink, User } from 'src/database';
|
||||
import { BulkIdErrorReason } from 'src/dtos/asset-ids.response.dto';
|
||||
import { AssetResponseDto, MapAsset, mapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
||||
|
|
@ -54,6 +55,24 @@ export class CreateAlbumDto {
|
|||
assetIds?: string[];
|
||||
}
|
||||
|
||||
export class AlbumsAddAssetsDto {
|
||||
@ValidateUUID({ each: true })
|
||||
albumIds!: string[];
|
||||
|
||||
@ValidateUUID({ each: true })
|
||||
assetIds!: string[];
|
||||
}
|
||||
|
||||
export class AlbumsAddAssetsResponseDto {
|
||||
success!: boolean;
|
||||
@ApiProperty({ type: 'integer' })
|
||||
albumSuccessCount!: number;
|
||||
@ApiProperty({ type: 'integer' })
|
||||
assetSuccessCount!: number;
|
||||
@ValidateEnum({ enum: BulkIdErrorReason, name: 'BulkIdErrorReason', optional: true })
|
||||
error?: BulkIdErrorReason;
|
||||
}
|
||||
|
||||
export class UpdateAlbumDto {
|
||||
@Optional()
|
||||
@IsString()
|
||||
|
|
|
|||
|
|
@ -776,6 +776,338 @@ describe(AlbumService.name, () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('addAssetsToAlbums', () => {
|
||||
it('should allow the owner to add assets', async () => {
|
||||
mocks.access.album.checkOwnerAccess
|
||||
.mockResolvedValueOnce(new Set(['album-123']))
|
||||
.mockResolvedValueOnce(new Set(['album-321']));
|
||||
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.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
||||
|
||||
await expect(
|
||||
sut.addAssetsToAlbums(authStub.admin, {
|
||||
albumIds: ['album-123', 'album-321'],
|
||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||
}),
|
||||
).resolves.toEqual({ success: true, albumSuccessCount: 2, assetSuccessCount: 3 });
|
||||
|
||||
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
||||
id: 'album-123',
|
||||
updatedAt: expect.any(Date),
|
||||
albumThumbnailAssetId: 'asset-1',
|
||||
});
|
||||
expect(mocks.album.update).toHaveBeenNthCalledWith(2, 'album-321', {
|
||||
id: 'album-321',
|
||||
updatedAt: expect.any(Date),
|
||||
albumThumbnailAssetId: 'asset-1',
|
||||
});
|
||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-321', ['asset-1', 'asset-2', 'asset-3']);
|
||||
});
|
||||
|
||||
it('should not set the thumbnail if the album has one already', async () => {
|
||||
mocks.access.album.checkOwnerAccess
|
||||
.mockResolvedValueOnce(new Set(['album-123']))
|
||||
.mockResolvedValueOnce(new Set(['album-321']));
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||
mocks.album.getById
|
||||
.mockResolvedValueOnce(_.cloneDeep({ ...albumStub.empty, albumThumbnailAssetId: 'asset-id' }))
|
||||
.mockResolvedValueOnce(_.cloneDeep({ ...albumStub.oneAsset, albumThumbnailAssetId: 'asset-id' }));
|
||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
||||
|
||||
await expect(
|
||||
sut.addAssetsToAlbums(authStub.admin, {
|
||||
albumIds: ['album-123', 'album-321'],
|
||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||
}),
|
||||
).resolves.toEqual({ success: true, albumSuccessCount: 2, assetSuccessCount: 3 });
|
||||
|
||||
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
||||
id: 'album-123',
|
||||
updatedAt: expect.any(Date),
|
||||
albumThumbnailAssetId: 'asset-id',
|
||||
});
|
||||
expect(mocks.album.update).toHaveBeenNthCalledWith(2, 'album-321', {
|
||||
id: 'album-321',
|
||||
updatedAt: expect.any(Date),
|
||||
albumThumbnailAssetId: 'asset-id',
|
||||
});
|
||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-321', ['asset-1', 'asset-2', 'asset-3']);
|
||||
});
|
||||
|
||||
it('should allow a shared user to add assets', async () => {
|
||||
mocks.access.album.checkSharedAlbumAccess
|
||||
.mockResolvedValueOnce(new Set(['album-123']))
|
||||
.mockResolvedValueOnce(new Set(['album-321']));
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||
mocks.album.getById
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithUser))
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithMultiple));
|
||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
||||
|
||||
await expect(
|
||||
sut.addAssetsToAlbums(authStub.user1, {
|
||||
albumIds: ['album-123', 'album-321'],
|
||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||
}),
|
||||
).resolves.toEqual({ success: true, albumSuccessCount: 2, assetSuccessCount: 3 });
|
||||
|
||||
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
||||
id: 'album-123',
|
||||
updatedAt: expect.any(Date),
|
||||
albumThumbnailAssetId: 'asset-1',
|
||||
});
|
||||
expect(mocks.album.update).toHaveBeenNthCalledWith(2, 'album-321', {
|
||||
id: 'album-321',
|
||||
updatedAt: expect.any(Date),
|
||||
albumThumbnailAssetId: 'asset-1',
|
||||
});
|
||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-321', ['asset-1', 'asset-2', 'asset-3']);
|
||||
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
|
||||
id: 'album-123',
|
||||
recipientId: 'admin_id',
|
||||
});
|
||||
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
|
||||
id: 'album-321',
|
||||
recipientId: 'admin_id',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow a shared user with viewer access to add assets', async () => {
|
||||
mocks.access.album.checkSharedAlbumAccess.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||
mocks.album.getById
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithUser))
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithAdmin));
|
||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
||||
|
||||
await expect(
|
||||
sut.addAssetsToAlbums(authStub.user2, {
|
||||
albumIds: ['album-123', 'album-321'],
|
||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
success: false,
|
||||
albumSuccessCount: 0,
|
||||
assetSuccessCount: 0,
|
||||
error: BulkIdErrorReason.UNKNOWN,
|
||||
});
|
||||
|
||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not allow a shared link user to add assets to multiple albums', async () => {
|
||||
mocks.access.album.checkSharedLinkAccess
|
||||
.mockResolvedValueOnce(new Set(['album-123']))
|
||||
.mockResolvedValueOnce(new Set());
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||
mocks.album.getById
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithUser))
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithMultiple));
|
||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
||||
|
||||
await expect(
|
||||
sut.addAssetsToAlbums(authStub.adminSharedLink, {
|
||||
albumIds: ['album-123', 'album-321'],
|
||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||
}),
|
||||
).resolves.toEqual({ success: true, albumSuccessCount: 1, assetSuccessCount: 3 });
|
||||
|
||||
expect(mocks.album.update).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
||||
id: 'album-123',
|
||||
updatedAt: expect.any(Date),
|
||||
albumThumbnailAssetId: 'asset-1',
|
||||
});
|
||||
expect(mocks.album.addAssetIds).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
||||
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
|
||||
id: 'album-123',
|
||||
recipientId: 'user-id',
|
||||
});
|
||||
expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalledWith(
|
||||
authStub.adminSharedLink.sharedLink?.id,
|
||||
new Set(['album-123']),
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow adding assets shared via partner sharing', async () => {
|
||||
mocks.access.album.checkOwnerAccess
|
||||
.mockResolvedValueOnce(new Set(['album-123']))
|
||||
.mockResolvedValueOnce(new Set(['album-321']));
|
||||
mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||
mocks.album.getById
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.empty))
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.oneAsset));
|
||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
||||
|
||||
await expect(
|
||||
sut.addAssetsToAlbums(authStub.admin, {
|
||||
albumIds: ['album-123', 'album-321'],
|
||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||
}),
|
||||
).resolves.toEqual({ success: true, albumSuccessCount: 2, assetSuccessCount: 3 });
|
||||
|
||||
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-123', {
|
||||
id: 'album-123',
|
||||
updatedAt: expect.any(Date),
|
||||
albumThumbnailAssetId: 'asset-1',
|
||||
});
|
||||
expect(mocks.album.update).toHaveBeenNthCalledWith(2, 'album-321', {
|
||||
id: 'album-321',
|
||||
updatedAt: expect.any(Date),
|
||||
albumThumbnailAssetId: 'asset-1',
|
||||
});
|
||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-321', ['asset-1', 'asset-2', 'asset-3']);
|
||||
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(
|
||||
authStub.admin.user.id,
|
||||
new Set(['asset-1', 'asset-2', 'asset-3']),
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip some duplicate assets', async () => {
|
||||
mocks.access.album.checkOwnerAccess
|
||||
.mockResolvedValueOnce(new Set(['album-123']))
|
||||
.mockResolvedValueOnce(new Set(['album-321']));
|
||||
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
|
||||
.mockResolvedValueOnce(new Set(['asset-1', 'asset-2', 'asset-3']))
|
||||
.mockResolvedValueOnce(new Set());
|
||||
|
||||
await expect(
|
||||
sut.addAssetsToAlbums(authStub.admin, {
|
||||
albumIds: ['album-123', 'album-321'],
|
||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||
}),
|
||||
).resolves.toEqual({ success: true, albumSuccessCount: 1, assetSuccessCount: 3 });
|
||||
|
||||
expect(mocks.album.update).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, 'album-321', {
|
||||
id: 'album-321',
|
||||
updatedAt: expect.any(Date),
|
||||
albumThumbnailAssetId: 'asset-1',
|
||||
});
|
||||
expect(mocks.album.addAssetIds).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith('album-321', ['asset-1', 'asset-2', 'asset-3']);
|
||||
});
|
||||
|
||||
it('should skip all duplicate assets', async () => {
|
||||
mocks.access.album.checkOwnerAccess
|
||||
.mockResolvedValueOnce(new Set(['album-123']))
|
||||
.mockResolvedValueOnce(new Set(['album-321']));
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2']));
|
||||
mocks.album.getById
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.empty))
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.oneAsset));
|
||||
mocks.album.getAssetIds.mockResolvedValue(new Set(['asset-1', 'asset-2']));
|
||||
|
||||
await expect(
|
||||
sut.addAssetsToAlbums(authStub.admin, {
|
||||
albumIds: ['album-123', 'album-321'],
|
||||
assetIds: ['asset-1', 'asset-2'],
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
success: false,
|
||||
albumSuccessCount: 0,
|
||||
assetSuccessCount: 0,
|
||||
error: BulkIdErrorReason.DUPLICATE,
|
||||
});
|
||||
|
||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||
expect(mocks.album.addAssetIds).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should skip assets not shared with user', async () => {
|
||||
mocks.access.album.checkSharedAlbumAccess
|
||||
.mockResolvedValueOnce(new Set(['album-123']))
|
||||
.mockResolvedValueOnce(new Set(['album-321']));
|
||||
mocks.album.getById
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithUser))
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithMultiple));
|
||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
||||
|
||||
await expect(
|
||||
sut.addAssetsToAlbums(authStub.admin, {
|
||||
albumIds: ['album-123', 'album-321'],
|
||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
success: false,
|
||||
albumSuccessCount: 0,
|
||||
assetSuccessCount: 0,
|
||||
error: BulkIdErrorReason.UNKNOWN,
|
||||
});
|
||||
|
||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||
expect(mocks.album.addAssetIds).not.toHaveBeenCalled();
|
||||
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(
|
||||
authStub.admin.user.id,
|
||||
new Set(['asset-1', 'asset-2', 'asset-3']),
|
||||
false,
|
||||
);
|
||||
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(
|
||||
authStub.admin.user.id,
|
||||
new Set(['asset-1', 'asset-2', 'asset-3']),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not allow unauthorized access to the albums', async () => {
|
||||
mocks.album.getById
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithUser))
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.sharedWithMultiple));
|
||||
|
||||
await expect(
|
||||
sut.addAssetsToAlbums(authStub.admin, {
|
||||
albumIds: ['album-123', 'album-321'],
|
||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
success: false,
|
||||
albumSuccessCount: 0,
|
||||
assetSuccessCount: 0,
|
||||
error: BulkIdErrorReason.UNKNOWN,
|
||||
});
|
||||
|
||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||
expect(mocks.album.addAssetIds).not.toHaveBeenCalled();
|
||||
expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalled();
|
||||
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not allow unauthorized shared link access to the album', async () => {
|
||||
mocks.album.getById
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.empty))
|
||||
.mockResolvedValueOnce(_.cloneDeep(albumStub.oneAsset));
|
||||
|
||||
await expect(
|
||||
sut.addAssetsToAlbums(authStub.adminSharedLink, {
|
||||
albumIds: ['album-123', 'album-321'],
|
||||
assetIds: ['asset-1', 'asset-2', 'asset-3'],
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
success: false,
|
||||
albumSuccessCount: 0,
|
||||
assetSuccessCount: 0,
|
||||
error: BulkIdErrorReason.UNKNOWN,
|
||||
});
|
||||
|
||||
expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeAssets', () => {
|
||||
it('should allow the owner to remove assets', async () => {
|
||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-123']));
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import {
|
|||
AddUsersDto,
|
||||
AlbumInfoDto,
|
||||
AlbumResponseDto,
|
||||
AlbumsAddAssetsDto,
|
||||
AlbumsAddAssetsResponseDto,
|
||||
AlbumStatisticsResponseDto,
|
||||
CreateAlbumDto,
|
||||
GetAlbumsDto,
|
||||
|
|
@ -13,7 +15,7 @@ import {
|
|||
UpdateAlbumDto,
|
||||
UpdateAlbumUserDto,
|
||||
} from 'src/dtos/album.dto';
|
||||
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||
import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { Permission } from 'src/enum';
|
||||
import { AlbumAssetCount, AlbumInfoOptions } from 'src/repositories/album.repository';
|
||||
|
|
@ -186,6 +188,43 @@ export class AlbumService extends BaseService {
|
|||
return results;
|
||||
}
|
||||
|
||||
async addAssetsToAlbums(auth: AuthDto, dto: AlbumsAddAssetsDto): Promise<AlbumsAddAssetsResponseDto> {
|
||||
const results: AlbumsAddAssetsResponseDto = {
|
||||
success: false,
|
||||
albumSuccessCount: 0,
|
||||
assetSuccessCount: 0,
|
||||
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;
|
||||
for (const res of albumResults) {
|
||||
if (res.success) {
|
||||
success = true;
|
||||
results.success = true;
|
||||
results.error = undefined;
|
||||
successfulAssetIds.add(res.id);
|
||||
} else if (results.error && res.error !== BulkIdErrorReason.DUPLICATE) {
|
||||
results.error = BulkIdErrorReason.UNKNOWN;
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
results.albumSuccessCount++;
|
||||
}
|
||||
} catch {
|
||||
if (results.error) {
|
||||
results.error = BulkIdErrorReason.UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
results.assetSuccessCount = successfulAssetIds.size;
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
||||
await this.requireAccess({ auth, permission: Permission.AlbumAssetDelete, ids: [id] });
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue