diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index 00708c9d1f..322f491695 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -2,7 +2,7 @@ import { BadRequestException } from '@nestjs/common'; import { DateTime } from 'luxon'; import { MapAsset } from 'src/dtos/asset-response.dto'; import { AssetJobName, AssetStatsResponseDto } from 'src/dtos/asset.dto'; -import { AssetStatus, AssetType, AssetVisibility, JobName, JobStatus } from 'src/enum'; +import { AssetMetadataKey, AssetStatus, AssetType, AssetVisibility, JobName, JobStatus } from 'src/enum'; import { AssetStats } from 'src/repositories/asset.repository'; import { AssetService } from 'src/services/asset.service'; import { assetStub } from 'test/fixtures/asset.stub'; @@ -777,4 +777,40 @@ describe(AssetService.name, () => { expect(result).toEqual(assets.map((asset) => asset.deviceAssetId)); }); }); + + describe('upsertMetadata', () => { + it('should throw a bad request exception if duplicate keys are sent', async () => { + const asset = factory.asset(); + const items = [ + { key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }, + { key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }, + ]; + + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); + + await expect(sut.upsertMetadata(authStub.admin, asset.id, { items })).rejects.toThrowError( + 'Duplicate items are not allowed:', + ); + + expect(mocks.asset.upsertBulkMetadata).not.toHaveBeenCalled(); + }); + }); + + describe('upsertBulkMetadata', () => { + it('should throw a bad request exception if duplicate keys are sent', async () => { + const asset = factory.asset(); + const items = [ + { assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }, + { assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }, + ]; + + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); + + await expect(sut.upsertBulkMetadata(authStub.admin, { items })).rejects.toThrowError( + 'Duplicate items are not allowed:', + ); + + expect(mocks.asset.upsertBulkMetadata).not.toHaveBeenCalled(); + }); + }); }); diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index 26775a5ce4..b92c339aab 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -414,11 +414,32 @@ export class AssetService extends BaseService { async upsertBulkMetadata(auth: AuthDto, dto: AssetMetadataBulkUpsertDto): Promise { await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: dto.items.map((item) => item.assetId) }); + + const uniqueKeys = new Set(); + for (const item of dto.items) { + const key = `(${item.assetId}, ${item.key})`; + if (uniqueKeys.has(key)) { + throw new BadRequestException(`Duplicate items are not allowed: "${key}"`); + } + + uniqueKeys.add(key); + } + return this.assetRepository.upsertBulkMetadata(dto.items); } async upsertMetadata(auth: AuthDto, id: string, dto: AssetMetadataUpsertDto): Promise { await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] }); + + const uniqueKeys = new Set(); + for (const { key } of dto.items) { + if (uniqueKeys.has(key)) { + throw new BadRequestException(`Duplicate items are not allowed: "${key}"`); + } + + uniqueKeys.add(key); + } + return this.assetRepository.upsertMetadata(id, dto.items); }