mirror of
https://github.com/immich-app/immich.git
synced 2026-09-30 13:23:21 +08:00
fix(server): emit sidecarwrites and assetupdates consequently (#31777)
* fix(server): emit sidecarwrites and assetupdates consequently * fix medium test
This commit is contained in:
@@ -184,6 +184,11 @@ describe(AssetService.name, () => {
|
||||
await sut.update(authStub.admin, asset.id, { isFavorite: true });
|
||||
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({ id: asset.id, isFavorite: true });
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith(
|
||||
'on_asset_update',
|
||||
authStub.admin.user.id,
|
||||
expect.objectContaining({ id: asset.id }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should update the exif description', async () => {
|
||||
@@ -220,6 +225,8 @@ describe(AssetService.name, () => {
|
||||
lockedPropertiesBehavior: 'append',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.SidecarWrite, data: { id: asset.id } });
|
||||
});
|
||||
|
||||
it('should fail linking a live video if the motion part could not be found', async () => {
|
||||
@@ -384,6 +391,22 @@ describe(AssetService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit a websocket event if a sidecar write is not necessary', async () => {
|
||||
const auth = AuthFactory.create();
|
||||
const asset1 = AssetFactory.from().owner(auth.user).build();
|
||||
const asset2 = AssetFactory.from().owner(auth.user).build();
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id]));
|
||||
mocks.asset.getByIds.mockResolvedValue([asset1, asset2]);
|
||||
|
||||
await sut.updateAll(auth, { ids: [asset1.id, asset2.id], visibility: AssetVisibility.Archive });
|
||||
|
||||
expect(mocks.asset.updateAll).toHaveBeenCalledWith([asset1.id, asset2.id], {
|
||||
visibility: AssetVisibility.Archive,
|
||||
});
|
||||
expect(mocks.job.queueAll).not.toHaveBeenCalled();
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should not update Assets table if no relevant fields are provided', async () => {
|
||||
const auth = AuthFactory.create();
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||
@@ -736,7 +759,7 @@ describe(AssetService.name, () => {
|
||||
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
|
||||
|
||||
await expect(sut.upsertMetadata(authStub.admin, asset.id, { items })).rejects.toThrowError(
|
||||
await expect(sut.upsertMetadata(authStub.admin, asset.id, { items })).rejects.toThrow(
|
||||
'Duplicate items are not allowed:',
|
||||
);
|
||||
|
||||
@@ -754,7 +777,7 @@ describe(AssetService.name, () => {
|
||||
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
|
||||
|
||||
await expect(sut.upsertBulkMetadata(authStub.admin, { items })).rejects.toThrowError(
|
||||
await expect(sut.upsertBulkMetadata(authStub.admin, { items })).rejects.toThrow(
|
||||
'Duplicate items are not allowed:',
|
||||
);
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ export class AssetService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateExif({ id, description, dateTimeOriginal, latitude, longitude, rating });
|
||||
const wroteMetadata = await this.updateExif({ id, description, dateTimeOriginal, latitude, longitude, rating });
|
||||
|
||||
const asset = await this.assetRepository.update({ id, ...rest });
|
||||
|
||||
@@ -129,6 +129,10 @@ export class AssetService extends BaseService {
|
||||
throw new BadRequestException('Asset not found');
|
||||
}
|
||||
|
||||
if (!wroteMetadata) {
|
||||
this.websocketRepository.clientSend('on_asset_update', auth.user.id, mapAsset(asset, { auth }));
|
||||
}
|
||||
|
||||
return this.get(auth, id) as Promise<AssetResponseDto>;
|
||||
}
|
||||
|
||||
@@ -160,8 +164,10 @@ export class AssetService extends BaseService {
|
||||
isUndefined,
|
||||
);
|
||||
|
||||
let shouldWriteSidecar = false;
|
||||
if (Object.keys(exifDto).length > 0) {
|
||||
await this.assetRepository.updateAllExif(ids, exifDto);
|
||||
shouldWriteSidecar = true;
|
||||
}
|
||||
|
||||
const extractedTimeZone = extractTimeZone(dateTimeOriginal);
|
||||
@@ -172,6 +178,7 @@ export class AssetService extends BaseService {
|
||||
extractedTimeZone?.type === 'fixed'
|
||||
) {
|
||||
await this.assetRepository.updateDateTimeOriginal(ids, dateTimeRelative, timeZone ?? extractedTimeZone?.name);
|
||||
shouldWriteSidecar = true;
|
||||
}
|
||||
|
||||
if (Object.keys(assetDto).length > 0) {
|
||||
@@ -182,7 +189,14 @@ export class AssetService extends BaseService {
|
||||
await this.albumRepository.removeAssetsFromAll(ids);
|
||||
}
|
||||
|
||||
await this.jobRepository.queueAll(ids.map((id) => ({ name: JobName.SidecarWrite, data: { id } })));
|
||||
if (shouldWriteSidecar) {
|
||||
await this.jobRepository.queueAll(ids.map((id) => ({ name: JobName.SidecarWrite, data: { id } })));
|
||||
} else {
|
||||
const assets = await this.assetRepository.getByIds(ids);
|
||||
for (const asset of assets) {
|
||||
this.websocketRepository.clientSend('on_asset_update', auth.user.id, mapAsset(asset, { auth }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async copy(
|
||||
@@ -508,7 +522,7 @@ export class AssetService extends BaseService {
|
||||
);
|
||||
|
||||
if (Object.keys(writes).length === 0) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.assetRepository.upsertExif({
|
||||
@@ -519,6 +533,7 @@ export class AssetService extends BaseService {
|
||||
lockedPropertiesBehavior: 'append',
|
||||
});
|
||||
await this.jobRepository.queue({ name: JobName.SidecarWrite, data: { id } });
|
||||
return true;
|
||||
}
|
||||
|
||||
async getAssetEdits(auth: AuthDto, id: string): Promise<AssetEditsResponseDto> {
|
||||
|
||||
@@ -64,6 +64,7 @@ import { TagRepository } from 'src/repositories/tag.repository.js';
|
||||
import { TelemetryRepository } from 'src/repositories/telemetry.repository.js';
|
||||
import { UserRepository } from 'src/repositories/user.repository.js';
|
||||
import { VersionHistoryRepository } from 'src/repositories/version-history.repository.js';
|
||||
import { WebsocketRepository } from 'src/repositories/websocket.repository.js';
|
||||
import { WorkflowRepository } from 'src/repositories/workflow.repository.js';
|
||||
import { DB } from 'src/schema/index.js';
|
||||
import { AlbumTable } from 'src/schema/tables/album.table.js';
|
||||
@@ -616,6 +617,10 @@ const newMockRepository = <T>(key: ClassConstructor<T>) => {
|
||||
return automock(StorageRepository, { args: [{ setContext: () => {} }] });
|
||||
}
|
||||
|
||||
case WebsocketRepository: {
|
||||
return automock(WebsocketRepository, { args: [undefined, { setContext: () => {} }] });
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(`Invalid repository key: ${key}`);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { LoggingRepository } from 'src/repositories/logging.repository.js';
|
||||
import { PluginRepository } from 'src/repositories/plugin.repository.js';
|
||||
import { StorageRepository } from 'src/repositories/storage.repository.js';
|
||||
import { UserRepository } from 'src/repositories/user.repository.js';
|
||||
import { WebsocketRepository } from 'src/repositories/websocket.repository.js';
|
||||
import { WorkflowRepository } from 'src/repositories/workflow.repository.js';
|
||||
import { DB } from 'src/schema/index.js';
|
||||
import { WorkflowExecutionService } from 'src/services/workflow-execution.service.js';
|
||||
@@ -40,7 +41,7 @@ class WorkflowTestContext extends MediumTestContext<typeof WorkflowExecutionServ
|
||||
UserRepository,
|
||||
WorkflowRepository,
|
||||
],
|
||||
mock: [ConfigRepository, EventRepository],
|
||||
mock: [ConfigRepository, EventRepository, WebsocketRepository],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,6 +55,7 @@ class WorkflowTestContext extends MediumTestContext<typeof WorkflowExecutionServ
|
||||
mockData.plugins.external.allow = false;
|
||||
this.getMock(ConfigRepository).getEnv.mockReturnValue(mockData);
|
||||
this.getMock(EventRepository).emit.mockResolvedValue();
|
||||
this.getMock(WebsocketRepository).clientSend.mockReturnValue();
|
||||
this.get(LoggingRepository).setLogLevel(LogLevel.Verbose);
|
||||
|
||||
await this.sut.onPluginSync();
|
||||
|
||||
Reference in New Issue
Block a user