diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index 322f491695..eca49bc14e 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -2,6 +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 { AssetEditAction } from 'src/dtos/editing.dto'; import { AssetMetadataKey, AssetStatus, AssetType, AssetVisibility, JobName, JobStatus } from 'src/enum'; import { AssetStats } from 'src/repositories/asset.repository'; import { AssetService } from 'src/services/asset.service'; @@ -813,4 +814,25 @@ describe(AssetService.name, () => { expect(mocks.asset.upsertBulkMetadata).not.toHaveBeenCalled(); }); }); + + describe('editAsset', () => { + it('should enforce crop first', async () => { + await expect( + sut.editAsset(authStub.admin, 'asset-1', { + edits: [ + { + action: AssetEditAction.Rotate, + parameters: { angle: 90 }, + }, + { + action: AssetEditAction.Crop, + parameters: { x: 0, y: 0, width: 100, height: 100 }, + }, + ], + }), + ).rejects.toBeInstanceOf(BadRequestException); + + expect(mocks.assetEdit.replaceAll).not.toHaveBeenCalled(); + }); + }); }); diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index 2616a6baf5..066084ed45 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -21,7 +21,7 @@ import { mapStats, } from 'src/dtos/asset.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { AssetEditAction, AssetEditActionListDto, AssetEditsDto } from 'src/dtos/editing.dto'; +import { AssetEditAction, AssetEditActionCrop, AssetEditActionListDto, AssetEditsDto } from 'src/dtos/editing.dto'; import { AssetOcrResponseDto } from 'src/dtos/ocr.dto'; import { AssetFileType, @@ -574,16 +574,21 @@ export class AssetService extends BaseService { throw new BadRequestException('Editing SVG images is not supported'); } - // check that crop parameters will not go out of bounds - const { width: assetWidth, height: assetHeight } = getDimensions(asset.exifInfo!); - - if (!assetWidth || !assetHeight) { - throw new BadRequestException('Asset dimensions are not available for editing'); + const cropIndex = dto.edits.findIndex((e) => e.action === AssetEditAction.Crop); + if (cropIndex > 0) { + throw new BadRequestException('Crop action must be the first edit action'); } - const crop = dto.edits.find((e) => e.action === AssetEditAction.Crop)?.parameters; + const crop = cropIndex === -1 ? null : (dto.edits[cropIndex] as AssetEditActionCrop); if (crop) { - const { x, y, width, height } = crop; + // check that crop parameters will not go out of bounds + const { width: assetWidth, height: assetHeight } = getDimensions(asset.exifInfo!); + + if (!assetWidth || !assetHeight) { + throw new BadRequestException('Asset dimensions are not available for editing'); + } + + const { x, y, width, height } = crop.parameters; if (x + width > assetWidth || y + height > assetHeight) { throw new BadRequestException('Crop parameters are out of bounds'); }