diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index 27e40139e1d93..f884a57e472ad 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -49,6 +49,23 @@ returning "dateTimeOriginal", "timeZone" +-- AssetRepository.unlockProperties +update "asset_exif" +set + "lockedProperties" = nullif( + array( + select distinct + property + from + unnest("asset_exif"."lockedProperties") property + where + not property = any ($1) + ), + '{}' + ) +where + "assetId" = $2 + -- AssetRepository.getMetadata select "key", diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index e1d16b8a6a189..1b7d9a20c8f1a 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -221,6 +221,17 @@ export class AssetRepository { .execute(); } + @GenerateSql({ params: [DummyValue.UUID, ['description']] }) + unlockProperties(assetId: string, properties: LockableProperty[]) { + return this.db + .updateTable('asset_exif') + .where('assetId', '=', assetId) + .set((eb) => ({ + lockedProperties: sql`nullif(array(select distinct property from unnest(${eb.ref('asset_exif.lockedProperties')}) property where not property = any(${properties})), '{}')`, + })) + .execute(); + } + async upsertJobStatus(...jobStatus: Insertable[]): Promise { if (jobStatus.length === 0) { return; diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index 98c906d9c7c4c..40e13b3b0c4ee 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -1705,6 +1705,12 @@ describe(MetadataService.name, () => { GPSLatitude: gps, GPSLongitude: gps, }); + expect(mocks.asset.unlockProperties).toHaveBeenCalledWith(asset.id, [ + 'description', + 'latitude', + 'longitude', + 'dateTimeOriginal', + ]); }); }); diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 3e5b220c0492e..f74855cf666b8 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -443,6 +443,8 @@ export class MetadataService extends BaseService { await this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.Sidecar, path: sidecarPath }); } + await this.assetRepository.unlockProperties(asset.id, lockedProperties); + return JobStatus.Success; } diff --git a/server/test/medium/specs/repositories/asset.repository.spec.ts b/server/test/medium/specs/repositories/asset.repository.spec.ts index a7af66f87294b..97f503e9edbc7 100644 --- a/server/test/medium/specs/repositories/asset.repository.spec.ts +++ b/server/test/medium/specs/repositories/asset.repository.spec.ts @@ -87,4 +87,64 @@ describe(AssetRepository.name, () => { ).resolves.toEqual({ lockedProperties: ['description', 'dateTimeOriginal'] }); }); }); + + describe('unlockProperties', () => { + it('should unlock one property', async () => { + const { ctx, sut } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newExif({ + assetId: asset.id, + dateTimeOriginal: '2023-11-19T18:11:00', + lockedProperties: ['dateTimeOriginal', 'description'], + }); + + await expect( + ctx.database + .selectFrom('asset_exif') + .select('lockedProperties') + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(), + ).resolves.toEqual({ lockedProperties: ['dateTimeOriginal', 'description'] }); + + await sut.unlockProperties(asset.id, ['dateTimeOriginal']); + + await expect( + ctx.database + .selectFrom('asset_exif') + .select('lockedProperties') + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(), + ).resolves.toEqual({ lockedProperties: ['description'] }); + }); + + it('should unlock all properties', async () => { + const { ctx, sut } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newExif({ + assetId: asset.id, + dateTimeOriginal: '2023-11-19T18:11:00', + lockedProperties: ['dateTimeOriginal', 'description'], + }); + + await expect( + ctx.database + .selectFrom('asset_exif') + .select('lockedProperties') + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(), + ).resolves.toEqual({ lockedProperties: ['dateTimeOriginal', 'description'] }); + + await sut.unlockProperties(asset.id, ['description', 'dateTimeOriginal']); + + await expect( + ctx.database + .selectFrom('asset_exif') + .select('lockedProperties') + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(), + ).resolves.toEqual({ lockedProperties: null }); + }); + }); }); diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index 4847c84a3508b..da57485382187 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -9,6 +9,7 @@ export const newAssetRepositoryMock = (): Mocked