1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-28 22:51:59 +00:00

chore(server): change save -> update in asset repository (#8055)

* `save` -> `update`

* change return type

* include relations

* fix tests

* remove when mocks

* fix

* stricter typing

* simpler type
This commit is contained in:
Mert 2024-03-19 22:42:10 -04:00 committed by GitHub
parent 9e4bab7494
commit 2daed747cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 128 additions and 138 deletions

View file

@ -548,19 +548,19 @@ describe(AssetService.name, () => {
await expect(sut.update(authStub.admin, 'asset-1', { isArchived: false })).rejects.toBeInstanceOf(
BadRequestException,
);
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
});
it('should update the asset', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
assetMock.save.mockResolvedValue(assetStub.image);
assetMock.getById.mockResolvedValue(assetStub.image);
await sut.update(authStub.admin, 'asset-1', { isFavorite: true });
expect(assetMock.save).toHaveBeenCalledWith({ id: 'asset-1', isFavorite: true });
expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-1', isFavorite: true });
});
it('should update the exif description', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
assetMock.save.mockResolvedValue(assetStub.image);
assetMock.getById.mockResolvedValue(assetStub.image);
await sut.update(authStub.admin, 'asset-1', { description: 'Test description' });
expect(assetMock.upsertExif).toHaveBeenCalledWith({ assetId: 'asset-1', description: 'Test description' });
});

View file

@ -324,7 +324,19 @@ export class AssetService {
const { description, dateTimeOriginal, latitude, longitude, ...rest } = dto;
await this.updateMetadata({ id, description, dateTimeOriginal, latitude, longitude });
const asset = await this.assetRepository.save({ id, ...rest });
await this.assetRepository.update({ id, ...rest });
const asset = await this.assetRepository.getById(id, {
exifInfo: true,
owner: true,
smartInfo: true,
tags: true,
faces: {
person: true,
},
});
if (!asset) {
throw new BadRequestException('Asset not found');
}
return mapAsset(asset, { auth });
}

View file

@ -93,27 +93,27 @@ export class AuditService {
switch (pathType) {
case AssetPathType.ENCODED_VIDEO: {
await this.assetRepository.save({ id, encodedVideoPath: pathValue });
await this.assetRepository.update({ id, encodedVideoPath: pathValue });
break;
}
case AssetPathType.JPEG_THUMBNAIL: {
await this.assetRepository.save({ id, resizePath: pathValue });
await this.assetRepository.update({ id, resizePath: pathValue });
break;
}
case AssetPathType.WEBP_THUMBNAIL: {
await this.assetRepository.save({ id, webpPath: pathValue });
await this.assetRepository.update({ id, webpPath: pathValue });
break;
}
case AssetPathType.ORIGINAL: {
await this.assetRepository.save({ id, originalPath: pathValue });
await this.assetRepository.update({ id, originalPath: pathValue });
break;
}
case AssetPathType.SIDECAR: {
await this.assetRepository.save({ id, sidecarPath: pathValue });
await this.assetRepository.update({ id, sidecarPath: pathValue });
break;
}

View file

@ -584,7 +584,7 @@ describe(LibraryService.name, () => {
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.save).toHaveBeenCalledWith({ id: assetStub.image.id, isOffline: true });
expect(assetMock.update).toHaveBeenCalledWith({ id: assetStub.image.id, isOffline: true });
expect(jobMock.queue).not.toHaveBeenCalled();
expect(jobMock.queueAll).not.toHaveBeenCalled();
});
@ -602,7 +602,7 @@ describe(LibraryService.name, () => {
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.save).toHaveBeenCalledWith({ id: assetStub.offline.id, isOffline: false });
expect(assetMock.update).toHaveBeenCalledWith({ id: assetStub.offline.id, isOffline: false });
expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.METADATA_EXTRACTION,
@ -631,7 +631,7 @@ describe(LibraryService.name, () => {
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image);
assetMock.create.mockResolvedValue(assetStub.image);
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
});
@ -1257,7 +1257,7 @@ describe(LibraryService.name, () => {
await sut.watchAll();
expect(assetMock.save).toHaveBeenCalledWith({ id: assetStub.external.id, isOffline: true });
expect(assetMock.update).toHaveBeenCalledWith({ id: assetStub.external.id, isOffline: true });
});
it('should handle an error event', async () => {

View file

@ -173,7 +173,7 @@ export class LibraryService extends EventEmitter {
this.logger.debug(`Detected deleted file at ${path} in library ${library.id}`);
const asset = await this.assetRepository.getByLibraryIdAndOriginalPath(library.id, path);
if (asset && matcher(path)) {
await this.assetRepository.save({ id: asset.id, isOffline: true });
await this.assetRepository.update({ id: asset.id, isOffline: true });
}
this.emit(StorageEventType.UNLINK, path);
};
@ -429,7 +429,7 @@ export class LibraryService extends EventEmitter {
// Mark asset as offline
this.logger.debug(`Marking asset as offline: ${assetPath}`);
await this.assetRepository.save({ id: existingAssetEntity.id, isOffline: true });
await this.assetRepository.update({ id: existingAssetEntity.id, isOffline: true });
return JobStatus.SUCCESS;
} else {
// File can't be accessed and does not already exist in db
@ -462,7 +462,7 @@ export class LibraryService extends EventEmitter {
if (stats && existingAssetEntity?.isOffline) {
// File was previously offline but is now online
this.logger.debug(`Marking previously-offline asset as online: ${assetPath}`);
await this.assetRepository.save({ id: existingAssetEntity.id, isOffline: false });
await this.assetRepository.update({ id: existingAssetEntity.id, isOffline: false });
doRefresh = true;
}

View file

@ -205,7 +205,7 @@ describe(MediaService.name, () => {
assetMock.getByIds.mockResolvedValue([]);
await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id });
expect(mediaMock.resize).not.toHaveBeenCalled();
expect(assetMock.save).not.toHaveBeenCalledWith();
expect(assetMock.update).not.toHaveBeenCalledWith();
});
it('should skip video thumbnail generation if no video stream', async () => {
@ -213,7 +213,7 @@ describe(MediaService.name, () => {
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id });
expect(mediaMock.resize).not.toHaveBeenCalled();
expect(assetMock.save).not.toHaveBeenCalledWith();
expect(assetMock.update).not.toHaveBeenCalledWith();
});
it('should generate a thumbnail for an image', async () => {
@ -227,7 +227,7 @@ describe(MediaService.name, () => {
quality: 80,
colorspace: Colorspace.SRGB,
});
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: 'asset-id',
resizePath: 'upload/thumbs/user-id/as/se/asset-id.jpeg',
});
@ -246,7 +246,7 @@ describe(MediaService.name, () => {
quality: 80,
colorspace: Colorspace.P3,
});
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: 'asset-id',
resizePath: 'upload/thumbs/user-id/as/se/asset-id.jpeg',
});
@ -271,7 +271,7 @@ describe(MediaService.name, () => {
twoPass: false,
},
);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: 'asset-id',
resizePath: 'upload/thumbs/user-id/as/se/asset-id.jpeg',
});
@ -296,7 +296,7 @@ describe(MediaService.name, () => {
twoPass: false,
},
);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: 'asset-id',
resizePath: 'upload/thumbs/user-id/as/se/asset-id.jpeg',
});
@ -337,7 +337,7 @@ describe(MediaService.name, () => {
assetMock.getByIds.mockResolvedValue([]);
await sut.handleGenerateWebpThumbnail({ id: assetStub.image.id });
expect(mediaMock.resize).not.toHaveBeenCalled();
expect(assetMock.save).not.toHaveBeenCalledWith();
expect(assetMock.update).not.toHaveBeenCalledWith();
});
it('should generate a thumbnail', async () => {
@ -350,7 +350,7 @@ describe(MediaService.name, () => {
quality: 80,
colorspace: Colorspace.SRGB,
});
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: 'asset-id',
webpPath: 'upload/thumbs/user-id/as/se/asset-id.webp',
});
@ -370,7 +370,7 @@ describe(MediaService.name, () => {
quality: 80,
colorspace: Colorspace.P3,
});
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: 'asset-id',
webpPath: 'upload/thumbs/user-id/as/se/asset-id.webp',
});
@ -397,7 +397,7 @@ describe(MediaService.name, () => {
await sut.handleGenerateThumbhashThumbnail({ id: assetStub.image.id });
expect(mediaMock.generateThumbhash).toHaveBeenCalledWith('/uploads/user-id/thumbs/path.jpg');
expect(assetMock.save).toHaveBeenCalledWith({ id: 'asset-id', thumbhash: thumbhashBuffer });
expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-id', thumbhash: thumbhashBuffer });
});
});

View file

@ -172,7 +172,7 @@ export class MediaService {
}
const resizePath = await this.generateThumbnail(asset, 'jpeg');
await this.assetRepository.save({ id: asset.id, resizePath });
await this.assetRepository.update({ id: asset.id, resizePath });
return JobStatus.SUCCESS;
}
@ -222,7 +222,7 @@ export class MediaService {
}
const webpPath = await this.generateThumbnail(asset, 'webp');
await this.assetRepository.save({ id: asset.id, webpPath });
await this.assetRepository.update({ id: asset.id, webpPath });
return JobStatus.SUCCESS;
}
@ -233,7 +233,7 @@ export class MediaService {
}
const thumbhash = await this.mediaRepository.generateThumbhash(asset.resizePath);
await this.assetRepository.save({ id: asset.id, thumbhash });
await this.assetRepository.update({ id: asset.id, thumbhash });
return JobStatus.SUCCESS;
}
@ -286,7 +286,7 @@ export class MediaService {
if (asset.encodedVideoPath) {
this.logger.log(`Transcoded video exists for asset ${asset.id}, but is no longer required. Deleting...`);
await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: [asset.encodedVideoPath] } });
await this.assetRepository.save({ id: asset.id, encodedVideoPath: null });
await this.assetRepository.update({ id: asset.id, encodedVideoPath: null });
}
return JobStatus.SKIPPED;
@ -321,7 +321,7 @@ export class MediaService {
this.logger.log(`Successfully encoded ${asset.id}`);
await this.assetRepository.save({ id: asset.id, encodedVideoPath: output });
await this.assetRepository.update({ id: asset.id, encodedVideoPath: output });
return JobStatus.SUCCESS;
}

View file

@ -117,7 +117,7 @@ describe(MetadataService.name, () => {
await expect(sut.handleLivePhotoLinking({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED);
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true });
expect(assetMock.findLivePhotoMatch).not.toHaveBeenCalled();
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
expect(albumMock.removeAsset).not.toHaveBeenCalled();
});
@ -127,7 +127,7 @@ describe(MetadataService.name, () => {
await expect(sut.handleLivePhotoLinking({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED);
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true });
expect(assetMock.findLivePhotoMatch).not.toHaveBeenCalled();
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
expect(albumMock.removeAsset).not.toHaveBeenCalled();
});
@ -137,7 +137,7 @@ describe(MetadataService.name, () => {
await expect(sut.handleLivePhotoLinking({ id: assetStub.image.id })).resolves.toBe(JobStatus.SKIPPED);
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true });
expect(assetMock.findLivePhotoMatch).not.toHaveBeenCalled();
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
expect(albumMock.removeAsset).not.toHaveBeenCalled();
});
@ -159,7 +159,7 @@ describe(MetadataService.name, () => {
otherAssetId: assetStub.livePhotoMotionAsset.id,
type: AssetType.IMAGE,
});
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
expect(albumMock.removeAsset).not.toHaveBeenCalled();
});
@ -182,11 +182,11 @@ describe(MetadataService.name, () => {
otherAssetId: assetStub.livePhotoStillAsset.id,
type: AssetType.VIDEO,
});
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.livePhotoStillAsset.id,
livePhotoVideoId: assetStub.livePhotoMotionAsset.id,
});
expect(assetMock.save).toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: false });
expect(assetMock.update).toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: false });
expect(albumMock.removeAsset).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.id);
});
@ -248,7 +248,7 @@ describe(MetadataService.name, () => {
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
expect(assetMock.upsertExif).not.toHaveBeenCalled();
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
});
it('should handle a date in a sidecar file', async () => {
@ -267,7 +267,7 @@ describe(MetadataService.name, () => {
await sut.handleMetadataExtraction({ id: assetStub.image.id });
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.sidecar.id]);
expect(assetMock.upsertExif).toHaveBeenCalledWith(expect.objectContaining({ dateTimeOriginal: sidecarDate }));
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.image.id,
duration: null,
fileCreatedAt: sidecarDate,
@ -282,7 +282,7 @@ describe(MetadataService.name, () => {
await sut.handleMetadataExtraction({ id: assetStub.image.id });
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
expect(assetMock.upsertExif).toHaveBeenCalledWith(expect.objectContaining({ iso: 160 }));
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.image.id,
duration: null,
fileCreatedAt: assetStub.image.createdAt,
@ -304,7 +304,7 @@ describe(MetadataService.name, () => {
expect(assetMock.upsertExif).toHaveBeenCalledWith(
expect.objectContaining({ city: 'City', state: 'State', country: 'Country' }),
);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.withLocation.id,
duration: null,
fileCreatedAt: assetStub.withLocation.createdAt,
@ -333,7 +333,7 @@ describe(MetadataService.name, () => {
expect(storageMock.writeFile).not.toHaveBeenCalled();
expect(jobMock.queue).not.toHaveBeenCalled();
expect(jobMock.queueAll).not.toHaveBeenCalled();
expect(assetMock.save).not.toHaveBeenCalledWith(
expect(assetMock.update).not.toHaveBeenCalledWith(
expect.objectContaining({ assetType: AssetType.VIDEO, isVisible: false }),
);
});
@ -376,7 +376,7 @@ describe(MetadataService.name, () => {
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoStillAsset.id]);
expect(assetMock.create).toHaveBeenCalled(); // This could have arguments added
expect(storageMock.writeFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video);
expect(assetMock.save).toHaveBeenNthCalledWith(1, {
expect(assetMock.update).toHaveBeenNthCalledWith(1, {
id: assetStub.livePhotoStillAsset.id,
livePhotoVideoId: fileStub.livePhotoMotion.uuid,
});
@ -404,7 +404,7 @@ describe(MetadataService.name, () => {
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoStillAsset.id]);
expect(assetMock.create).toHaveBeenCalled(); // This could have arguments added
expect(storageMock.writeFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video);
expect(assetMock.save).toHaveBeenNthCalledWith(1, {
expect(assetMock.update).toHaveBeenNthCalledWith(1, {
id: assetStub.livePhotoStillAsset.id,
livePhotoVideoId: fileStub.livePhotoMotion.uuid,
});
@ -430,7 +430,7 @@ describe(MetadataService.name, () => {
expect(storageMock.readFile).toHaveBeenCalledWith(assetStub.livePhotoStillAsset.originalPath, expect.any(Object));
expect(assetMock.create).toHaveBeenCalled(); // This could have arguments added
expect(storageMock.writeFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video);
expect(assetMock.save).toHaveBeenNthCalledWith(1, {
expect(assetMock.update).toHaveBeenNthCalledWith(1, {
id: assetStub.livePhotoStillAsset.id,
livePhotoVideoId: fileStub.livePhotoMotion.uuid,
});
@ -470,7 +470,7 @@ describe(MetadataService.name, () => {
expect(assetMock.create).toHaveBeenCalledTimes(0);
expect(storageMock.writeFile).toHaveBeenCalledTimes(0);
// The still asset gets saved by handleMetadataExtraction, but not the video
expect(assetMock.save).toHaveBeenCalledTimes(1);
expect(assetMock.update).toHaveBeenCalledTimes(1);
expect(jobMock.queue).toHaveBeenCalledTimes(0);
});
@ -529,7 +529,7 @@ describe(MetadataService.name, () => {
projectionType: 'EQUIRECTANGULAR',
timeZone: tags.tz,
});
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.image.id,
duration: null,
fileCreatedAt: new Date('1970-01-01'),
@ -545,7 +545,7 @@ describe(MetadataService.name, () => {
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
expect(assetMock.upsertExif).toHaveBeenCalled();
expect(assetMock.save).toHaveBeenCalledWith(
expect(assetMock.update).toHaveBeenCalledWith(
expect.objectContaining({
id: assetStub.image.id,
duration: '00:00:06.210',
@ -561,7 +561,7 @@ describe(MetadataService.name, () => {
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
expect(assetMock.upsertExif).toHaveBeenCalled();
expect(assetMock.save).toHaveBeenCalledWith(
expect(assetMock.update).toHaveBeenCalledWith(
expect.objectContaining({
id: assetStub.image.id,
duration: '00:00:08.410',
@ -577,7 +577,7 @@ describe(MetadataService.name, () => {
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
expect(assetMock.upsertExif).toHaveBeenCalled();
expect(assetMock.save).toHaveBeenCalledWith(
expect(assetMock.update).toHaveBeenCalledWith(
expect.objectContaining({
id: assetStub.image.id,
duration: '00:00:06.200',
@ -593,7 +593,7 @@ describe(MetadataService.name, () => {
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
expect(assetMock.upsertExif).toHaveBeenCalled();
expect(assetMock.save).toHaveBeenCalledWith(
expect(assetMock.update).toHaveBeenCalledWith(
expect.objectContaining({
id: assetStub.image.id,
duration: '00:00:06.207',
@ -638,13 +638,13 @@ describe(MetadataService.name, () => {
it('should do nothing if asset could not be found', async () => {
assetMock.getByIds.mockResolvedValue([]);
await expect(sut.handleSidecarSync({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED);
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
});
it('should do nothing if asset has no sidecar path', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
await expect(sut.handleSidecarSync({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED);
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
});
it('should set sidecar path if exists (sidecar named photo.ext.xmp)', async () => {
@ -653,7 +653,7 @@ describe(MetadataService.name, () => {
await expect(sut.handleSidecarSync({ id: assetStub.sidecar.id })).resolves.toBe(JobStatus.SUCCESS);
expect(storageMock.checkFileExists).toHaveBeenCalledWith(`${assetStub.sidecar.originalPath}.xmp`, constants.R_OK);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.sidecar.id,
sidecarPath: assetStub.sidecar.sidecarPath,
});
@ -670,7 +670,7 @@ describe(MetadataService.name, () => {
assetStub.sidecarWithoutExt.sidecarPath,
constants.R_OK,
);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.sidecarWithoutExt.id,
sidecarPath: assetStub.sidecarWithoutExt.sidecarPath,
});
@ -688,7 +688,7 @@ describe(MetadataService.name, () => {
assetStub.sidecarWithoutExt.sidecarPath,
constants.R_OK,
);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.sidecar.id,
sidecarPath: assetStub.sidecar.sidecarPath,
});
@ -700,7 +700,7 @@ describe(MetadataService.name, () => {
await expect(sut.handleSidecarSync({ id: assetStub.sidecar.id })).resolves.toBe(JobStatus.SUCCESS);
expect(storageMock.checkFileExists).toHaveBeenCalledWith(`${assetStub.sidecar.originalPath}.xmp`, constants.R_OK);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.sidecar.id,
sidecarPath: null,
});
@ -724,16 +724,15 @@ describe(MetadataService.name, () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
storageMock.checkFileExists.mockResolvedValue(false);
await sut.handleSidecarDiscovery({ id: assetStub.image.id });
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
});
it('should update a image asset when a sidecar is found', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
assetMock.save.mockResolvedValue(assetStub.image);
storageMock.checkFileExists.mockResolvedValue(true);
await sut.handleSidecarDiscovery({ id: assetStub.image.id });
expect(storageMock.checkFileExists).toHaveBeenCalledWith('/original/path.jpg.xmp', constants.R_OK);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.image.id,
sidecarPath: '/original/path.jpg.xmp',
});
@ -741,11 +740,10 @@ describe(MetadataService.name, () => {
it('should update a video asset when a sidecar is found', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.video]);
assetMock.save.mockResolvedValue(assetStub.video);
storageMock.checkFileExists.mockResolvedValue(true);
await sut.handleSidecarDiscovery({ id: assetStub.video.id });
expect(storageMock.checkFileExists).toHaveBeenCalledWith('/original/path.ext.xmp', constants.R_OK);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.image.id,
sidecarPath: '/original/path.ext.xmp',
});

View file

@ -177,8 +177,8 @@ export class MetadataService {
const [photoAsset, motionAsset] = asset.type === AssetType.IMAGE ? [asset, match] : [match, asset];
await this.assetRepository.save({ id: photoAsset.id, livePhotoVideoId: motionAsset.id });
await this.assetRepository.save({ id: motionAsset.id, isVisible: false });
await this.assetRepository.update({ id: photoAsset.id, livePhotoVideoId: motionAsset.id });
await this.assetRepository.update({ id: motionAsset.id, isVisible: false });
await this.albumRepository.removeAsset(motionAsset.id);
// Notify clients to hide the linked live photo asset
@ -249,7 +249,7 @@ export class MetadataService {
if (dateTimeOriginal && timeZoneOffset) {
localDateTime = new Date(dateTimeOriginal.getTime() + timeZoneOffset * 60_000);
}
await this.assetRepository.save({
await this.assetRepository.update({
id: asset.id,
duration: tags.Duration ? this.getDuration(tags.Duration) : null,
localDateTime,
@ -317,7 +317,7 @@ export class MetadataService {
await this.repository.writeTags(sidecarPath, exif);
if (!asset.sidecarPath) {
await this.assetRepository.save({ id, sidecarPath });
await this.assetRepository.update({ id, sidecarPath });
}
return JobStatus.SUCCESS;
@ -435,7 +435,7 @@ export class MetadataService {
this.storageCore.ensureFolders(motionPath);
await this.storageRepository.writeFile(motionAsset.originalPath, video);
await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: motionAsset.id } });
await this.assetRepository.save({ id: asset.id, livePhotoVideoId: motionAsset.id });
await this.assetRepository.update({ id: asset.id, livePhotoVideoId: motionAsset.id });
// If the asset already had an associated livePhotoVideo, delete it, because
// its checksum doesn't match the checksum of the motionAsset we just extracted
@ -587,7 +587,7 @@ export class MetadataService {
}
if (sidecarPath) {
await this.assetRepository.save({ id: asset.id, sidecarPath });
await this.assetRepository.update({ id: asset.id, sidecarPath });
return JobStatus.SUCCESS;
}
@ -598,7 +598,7 @@ export class MetadataService {
this.logger.debug(
`Sidecar file was not found. Checked paths '${sidecarPathWithExt}' and '${sidecarPathWithoutExt}'. Removing sidecarPath for asset ${asset.id}`,
);
await this.assetRepository.save({ id: asset.id, sidecarPath: null });
await this.assetRepository.update({ id: asset.id, sidecarPath: null });
return JobStatus.SUCCESS;
}

View file

@ -90,6 +90,25 @@ export type AssetCreate = Pick<
> &
Partial<AssetEntity>;
export type AssetWithoutRelations = Omit<
AssetEntity,
| 'livePhotoVideo'
| 'stack'
| 'albums'
| 'faces'
| 'owner'
| 'library'
| 'exifInfo'
| 'sharedLinks'
| 'smartInfo'
| 'smartSearch'
| 'tags'
>;
export type AssetUpdateOptions = Pick<AssetWithoutRelations, 'id'> & Partial<AssetWithoutRelations>;
export type AssetUpdateAllOptions = Omit<Partial<AssetWithoutRelations>, 'id'>;
export interface MonthDay {
day: number;
month: number;
@ -138,8 +157,8 @@ export interface IAssetRepository {
deleteAll(ownerId: string): Promise<void>;
getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated<AssetEntity>;
getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>;
updateAll(ids: string[], options: Partial<AssetEntity>): Promise<void>;
save(asset: Pick<AssetEntity, 'id'> & Partial<AssetEntity>): Promise<AssetEntity>;
updateAll(ids: string[], options: Partial<AssetUpdateAllOptions>): Promise<void>;
update(asset: AssetUpdateOptions): Promise<void>;
remove(asset: AssetEntity): Promise<void>;
softDeleteAll(ids: string[]): Promise<void>;
restoreAll(ids: string[]): Promise<void>;

View file

@ -111,7 +111,7 @@ describe(StorageTemplateService.name, () => {
expect(storageMock.checkFileExists).not.toHaveBeenCalled();
expect(storageMock.rename).not.toHaveBeenCalled();
expect(storageMock.copyFile).not.toHaveBeenCalled();
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
expect(moveMock.create).not.toHaveBeenCalled();
expect(moveMock.update).not.toHaveBeenCalled();
expect(storageMock.stat).not.toHaveBeenCalled();
@ -122,14 +122,6 @@ describe(StorageTemplateService.name, () => {
const newMotionPicturePath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${assetStub.livePhotoStillAsset.id}.mp4`;
const newStillPicturePath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${assetStub.livePhotoStillAsset.id}.jpeg`;
when(assetMock.save)
.calledWith({ id: assetStub.livePhotoStillAsset.id, originalPath: newStillPicturePath })
.mockResolvedValue(assetStub.livePhotoStillAsset);
when(assetMock.save)
.calledWith({ id: assetStub.livePhotoMotionAsset.id, originalPath: newMotionPicturePath })
.mockResolvedValue(assetStub.livePhotoMotionAsset);
when(assetMock.getByIds)
.calledWith([assetStub.livePhotoStillAsset.id], { exifInfo: true })
.mockResolvedValue([assetStub.livePhotoStillAsset]);
@ -175,11 +167,11 @@ describe(StorageTemplateService.name, () => {
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoStillAsset.id], { exifInfo: true });
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoMotionAsset.id], { exifInfo: true });
expect(storageMock.checkFileExists).toHaveBeenCalledTimes(2);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.livePhotoStillAsset.id,
originalPath: newStillPicturePath,
});
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.livePhotoMotionAsset.id,
originalPath: newMotionPicturePath,
});
@ -200,10 +192,6 @@ describe(StorageTemplateService.name, () => {
newPath: previousFailedNewPath,
});
when(assetMock.save)
.calledWith({ id: assetStub.image.id, originalPath: newPath })
.mockResolvedValue(assetStub.image);
when(assetMock.getByIds)
.calledWith([assetStub.image.id], { exifInfo: true })
.mockResolvedValue([assetStub.image]);
@ -232,7 +220,7 @@ describe(StorageTemplateService.name, () => {
oldPath: assetStub.image.originalPath,
newPath,
});
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.image.id,
originalPath: newPath,
});
@ -257,10 +245,6 @@ describe(StorageTemplateService.name, () => {
newPath: previousFailedNewPath,
});
when(assetMock.save)
.calledWith({ id: assetStub.image.id, originalPath: newPath })
.mockResolvedValue(assetStub.image);
when(assetMock.getByIds)
.calledWith([assetStub.image.id], { exifInfo: true })
.mockResolvedValue([assetStub.image]);
@ -291,7 +275,7 @@ describe(StorageTemplateService.name, () => {
oldPath: previousFailedNewPath,
newPath,
});
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.image.id,
originalPath: newPath,
});
@ -307,10 +291,6 @@ describe(StorageTemplateService.name, () => {
.mockResolvedValue({ size: 5000 } as Stats);
when(cryptoMock.hashFile).calledWith(newPath).mockResolvedValue(Buffer.from('different-hash', 'utf8'));
when(assetMock.save)
.calledWith({ id: assetStub.image.id, originalPath: newPath })
.mockResolvedValue(assetStub.image);
when(assetMock.getByIds)
.calledWith([assetStub.image.id], { exifInfo: true })
.mockResolvedValue([assetStub.image]);
@ -345,7 +325,7 @@ describe(StorageTemplateService.name, () => {
expect(storageMock.copyFile).toHaveBeenCalledWith(assetStub.image.originalPath, newPath);
expect(storageMock.unlink).toHaveBeenCalledWith(newPath);
expect(storageMock.unlink).toHaveBeenCalledTimes(1);
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
});
it.each`
@ -374,10 +354,6 @@ describe(StorageTemplateService.name, () => {
newPath: previousFailedNewPath,
});
when(assetMock.save)
.calledWith({ id: assetStub.image.id, originalPath: newPath })
.mockResolvedValue(assetStub.image);
when(assetMock.getByIds)
.calledWith([assetStub.image.id], { exifInfo: true })
.mockResolvedValue([assetStub.image]);
@ -404,7 +380,7 @@ describe(StorageTemplateService.name, () => {
expect(storageMock.rename).not.toHaveBeenCalled();
expect(storageMock.copyFile).not.toHaveBeenCalled();
expect(moveMock.update).not.toHaveBeenCalled();
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
},
);
});
@ -427,7 +403,6 @@ describe(StorageTemplateService.name, () => {
items: [assetStub.image],
hasNextPage: false,
});
assetMock.save.mockResolvedValue(assetStub.image);
userMock.getList.mockResolvedValue([userStub.user1]);
moveMock.create.mockResolvedValue({
id: '123',
@ -449,7 +424,7 @@ describe(StorageTemplateService.name, () => {
expect(assetMock.getAll).toHaveBeenCalled();
expect(storageMock.checkFileExists).toHaveBeenCalledTimes(2);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.image.id,
originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg',
});
@ -474,7 +449,7 @@ describe(StorageTemplateService.name, () => {
expect(storageMock.rename).not.toHaveBeenCalled();
expect(storageMock.copyFile).not.toHaveBeenCalled();
expect(storageMock.checkFileExists).not.toHaveBeenCalledTimes(2);
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
});
it('should skip when an asset is probably a duplicate', async () => {
@ -495,7 +470,7 @@ describe(StorageTemplateService.name, () => {
expect(storageMock.rename).not.toHaveBeenCalled();
expect(storageMock.copyFile).not.toHaveBeenCalled();
expect(storageMock.checkFileExists).not.toHaveBeenCalledTimes(2);
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
});
it('should move an asset', async () => {
@ -503,7 +478,6 @@ describe(StorageTemplateService.name, () => {
items: [assetStub.image],
hasNextPage: false,
});
assetMock.save.mockResolvedValue(assetStub.image);
userMock.getList.mockResolvedValue([userStub.user1]);
moveMock.create.mockResolvedValue({
id: '123',
@ -520,7 +494,7 @@ describe(StorageTemplateService.name, () => {
'/original/path.jpg',
'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.image.id,
originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
});
@ -531,7 +505,6 @@ describe(StorageTemplateService.name, () => {
items: [assetStub.image],
hasNextPage: false,
});
assetMock.save.mockResolvedValue(assetStub.image);
userMock.getList.mockResolvedValue([userStub.storageLabel]);
moveMock.create.mockResolvedValue({
id: '123',
@ -548,7 +521,7 @@ describe(StorageTemplateService.name, () => {
'/original/path.jpg',
'upload/library/label-1/2023/2023-02-23/asset-id.jpg',
);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.image.id,
originalPath: 'upload/library/label-1/2023/2023-02-23/asset-id.jpg',
});
@ -592,7 +565,7 @@ describe(StorageTemplateService.name, () => {
expect(storageMock.utimes).toHaveBeenCalledWith(newPath, expect.any(Date), expect.any(Date));
expect(storageMock.unlink).toHaveBeenCalledWith(assetStub.image.originalPath);
expect(storageMock.unlink).toHaveBeenCalledTimes(1);
expect(assetMock.save).toHaveBeenCalledWith({
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.image.id,
originalPath: newPath,
});
@ -630,7 +603,7 @@ describe(StorageTemplateService.name, () => {
'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
);
expect(storageMock.stat).toHaveBeenCalledWith('upload/library/user-id/2023/2023-02-23/asset-id.jpg');
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
});
it('should not update the database if the move fails', async () => {
@ -656,7 +629,7 @@ describe(StorageTemplateService.name, () => {
'/original/path.jpg',
'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
);
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
});
it('should not move read-only asset', async () => {
@ -670,7 +643,6 @@ describe(StorageTemplateService.name, () => {
],
hasNextPage: false,
});
assetMock.save.mockResolvedValue(assetStub.image);
userMock.getList.mockResolvedValue([userStub.user1]);
await sut.handleMigration();
@ -678,7 +650,7 @@ describe(StorageTemplateService.name, () => {
expect(assetMock.getAll).toHaveBeenCalled();
expect(storageMock.rename).not.toHaveBeenCalled();
expect(storageMock.copyFile).not.toHaveBeenCalled();
expect(assetMock.save).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
});
});
});

View file

@ -286,19 +286,19 @@ export class StorageCore {
private savePath(pathType: PathType, id: string, newPath: string) {
switch (pathType) {
case AssetPathType.ORIGINAL: {
return this.assetRepository.save({ id, originalPath: newPath });
return this.assetRepository.update({ id, originalPath: newPath });
}
case AssetPathType.JPEG_THUMBNAIL: {
return this.assetRepository.save({ id, resizePath: newPath });
return this.assetRepository.update({ id, resizePath: newPath });
}
case AssetPathType.WEBP_THUMBNAIL: {
return this.assetRepository.save({ id, webpPath: newPath });
return this.assetRepository.update({ id, webpPath: newPath });
}
case AssetPathType.ENCODED_VIDEO: {
return this.assetRepository.save({ id, encodedVideoPath: newPath });
return this.assetRepository.update({ id, encodedVideoPath: newPath });
}
case AssetPathType.SIDECAR: {
return this.assetRepository.save({ id, sidecarPath: newPath });
return this.assetRepository.update({ id, sidecarPath: newPath });
}
case PersonPathType.FACE: {
return this.personRepository.update({ id, thumbnailPath: newPath });

View file

@ -6,6 +6,8 @@ import {
AssetSearchOptions,
AssetStats,
AssetStatsOptions,
AssetUpdateAllOptions,
AssetUpdateOptions,
IAssetRepository,
LivePhotoSearchOptions,
MapMarker,
@ -275,7 +277,7 @@ export class AssetRepository implements IAssetRepository {
@GenerateSql({ params: [[DummyValue.UUID], { deviceId: DummyValue.STRING }] })
@Chunked()
async updateAll(ids: string[], options: Partial<AssetEntity>): Promise<void> {
async updateAll(ids: string[], options: AssetUpdateAllOptions): Promise<void> {
await this.repository.update({ id: In(ids) }, options);
}
@ -289,21 +291,8 @@ export class AssetRepository implements IAssetRepository {
await this.repository.restore({ id: In(ids) });
}
async save(asset: Partial<AssetEntity>): Promise<AssetEntity> {
const { id } = await this.repository.save(asset);
return this.repository.findOneOrFail({
where: { id },
relations: {
exifInfo: true,
owner: true,
smartInfo: true,
tags: true,
faces: {
person: true,
},
},
withDeleted: true,
});
async update(asset: AssetUpdateOptions): Promise<void> {
await this.repository.update(asset.id, asset);
}
async remove(asset: AssetEntity): Promise<void> {

View file

@ -24,7 +24,7 @@ export const newAssetRepositoryMock = (): jest.Mocked<IAssetRepository> => {
getLibraryAssetPaths: jest.fn(),
getByLibraryIdAndOriginalPath: jest.fn(),
deleteAll: jest.fn(),
save: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
findLivePhotoMatch: jest.fn(),
getMapMarkers: jest.fn(),