merge: remote-tracking branch 'origin/main' into feat/integrity-checks-izzy

This commit is contained in:
izzy
2026-01-15 17:08:06 +00:00
114 changed files with 11993 additions and 1358 deletions

View File

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

View File

@@ -414,11 +414,32 @@ export class AssetService extends BaseService {
async upsertBulkMetadata(auth: AuthDto, dto: AssetMetadataBulkUpsertDto): Promise<AssetMetadataBulkResponseDto[]> {
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: dto.items.map((item) => item.assetId) });
const uniqueKeys = new Set<string>();
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<AssetMetadataResponseDto[]> {
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] });
const uniqueKeys = new Set<string>();
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);
}

View File

@@ -261,6 +261,7 @@ export const useSwagger = (app: INestApplication, { write }: { write: boolean })
const options: SwaggerDocumentOptions = {
operationIdFactory: (controllerKey: string, methodKey: string) => methodKey,
extraModels: extraSyncModels,
ignoreGlobalPrefix: true,
};
const specification = SwaggerModule.createDocument(app, config, options);