mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
refactor: websocket repository (#23228)
This commit is contained in:
@@ -51,6 +51,7 @@ import { TrashRepository } from 'src/repositories/trash.repository';
|
||||
import { UserRepository } from 'src/repositories/user.repository';
|
||||
import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
|
||||
import { ViewRepository } from 'src/repositories/view-repository';
|
||||
import { WebsocketRepository } from 'src/repositories/websocket.repository';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access';
|
||||
import { getConfig, updateConfig } from 'src/utils/config';
|
||||
@@ -155,6 +156,7 @@ export class BaseService {
|
||||
protected userRepository: UserRepository,
|
||||
protected versionRepository: VersionHistoryRepository,
|
||||
protected viewRepository: ViewRepository,
|
||||
protected websocketRepository: WebsocketRepository,
|
||||
) {
|
||||
this.logger.setContext(this.constructor.name);
|
||||
this.storageCore = StorageCore.create(
|
||||
|
||||
@@ -331,7 +331,7 @@ export class JobService extends BaseService {
|
||||
const { id } = item.data;
|
||||
const person = await this.personRepository.getById(id);
|
||||
if (person) {
|
||||
this.eventRepository.clientSend('on_person_thumbnail', person.ownerId, person.id);
|
||||
this.websocketRepository.clientSend('on_person_thumbnail', person.ownerId, person.id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -358,10 +358,10 @@ export class JobService extends BaseService {
|
||||
|
||||
await this.jobRepository.queueAll(jobs);
|
||||
if (asset.visibility === AssetVisibility.Timeline || asset.visibility === AssetVisibility.Archive) {
|
||||
this.eventRepository.clientSend('on_upload_success', asset.ownerId, mapAsset(asset));
|
||||
this.websocketRepository.clientSend('on_upload_success', asset.ownerId, mapAsset(asset));
|
||||
if (asset.exifInfo) {
|
||||
const exif = asset.exifInfo;
|
||||
this.eventRepository.clientSend('AssetUploadReadyV1', asset.ownerId, {
|
||||
this.websocketRepository.clientSend('AssetUploadReadyV1', asset.ownerId, {
|
||||
// TODO remove `on_upload_success` and then modify the query to select only the required fields)
|
||||
asset: {
|
||||
id: asset.id,
|
||||
|
||||
@@ -65,8 +65,8 @@ describe(NotificationService.name, () => {
|
||||
it('should emit client and server events', () => {
|
||||
const update = { oldConfig: defaults, newConfig: defaults };
|
||||
expect(sut.onConfigUpdate(update)).toBeUndefined();
|
||||
expect(mocks.event.clientBroadcast).toHaveBeenCalledWith('on_config_update');
|
||||
expect(mocks.event.serverSend).toHaveBeenCalledWith('ConfigUpdate', update);
|
||||
expect(mocks.websocket.clientBroadcast).toHaveBeenCalledWith('on_config_update');
|
||||
expect(mocks.websocket.serverSend).toHaveBeenCalledWith('ConfigUpdate', update);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,7 +125,7 @@ describe(NotificationService.name, () => {
|
||||
describe('onAssetHide', () => {
|
||||
it('should send connected clients an event', () => {
|
||||
sut.onAssetHide({ assetId: 'asset-id', userId: 'user-id' });
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_hidden', 'user-id', 'asset-id');
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_hidden', 'user-id', 'asset-id');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -178,67 +178,67 @@ describe(NotificationService.name, () => {
|
||||
it('should send a on_session_delete client event', () => {
|
||||
vi.useFakeTimers();
|
||||
sut.onSessionDelete({ sessionId: 'id' });
|
||||
expect(mocks.event.clientSend).not.toHaveBeenCalled();
|
||||
expect(mocks.websocket.clientSend).not.toHaveBeenCalled();
|
||||
|
||||
vi.advanceTimersByTime(500);
|
||||
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledWith('on_session_delete', 'id', 'id');
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_session_delete', 'id', 'id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onAssetTrash', () => {
|
||||
it('should send connected clients an event', () => {
|
||||
it('should send connected clients an websocket', () => {
|
||||
sut.onAssetTrash({ assetId: 'asset-id', userId: 'user-id' });
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_trash', 'user-id', ['asset-id']);
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_trash', 'user-id', ['asset-id']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onAssetDelete', () => {
|
||||
it('should send connected clients an event', () => {
|
||||
sut.onAssetDelete({ assetId: 'asset-id', userId: 'user-id' });
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_delete', 'user-id', 'asset-id');
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_delete', 'user-id', 'asset-id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onAssetsTrash', () => {
|
||||
it('should send connected clients an event', () => {
|
||||
sut.onAssetsTrash({ assetIds: ['asset-id'], userId: 'user-id' });
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_trash', 'user-id', ['asset-id']);
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_trash', 'user-id', ['asset-id']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onAssetsRestore', () => {
|
||||
it('should send connected clients an event', () => {
|
||||
sut.onAssetsRestore({ assetIds: ['asset-id'], userId: 'user-id' });
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_restore', 'user-id', ['asset-id']);
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_restore', 'user-id', ['asset-id']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onStackCreate', () => {
|
||||
it('should send connected clients an event', () => {
|
||||
sut.onStackCreate({ stackId: 'stack-id', userId: 'user-id' });
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id');
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onStackUpdate', () => {
|
||||
it('should send connected clients an event', () => {
|
||||
sut.onStackUpdate({ stackId: 'stack-id', userId: 'user-id' });
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id');
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onStackDelete', () => {
|
||||
it('should send connected clients an event', () => {
|
||||
sut.onStackDelete({ stackId: 'stack-id', userId: 'user-id' });
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id');
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onStacksDelete', () => {
|
||||
it('should send connected clients an event', () => {
|
||||
sut.onStacksDelete({ stackIds: ['stack-id'], userId: 'user-id' });
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id');
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ export class NotificationService extends BaseService {
|
||||
description: `Job ${[job.name]} failed with error: ${errorMessage}`,
|
||||
});
|
||||
|
||||
this.eventRepository.clientSend('on_notification', admin.id, mapNotification(item));
|
||||
this.websocketRepository.clientSend('on_notification', admin.id, mapNotification(item));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -110,8 +110,8 @@ export class NotificationService extends BaseService {
|
||||
|
||||
@OnEvent({ name: 'ConfigUpdate' })
|
||||
onConfigUpdate({ oldConfig, newConfig }: ArgOf<'ConfigUpdate'>) {
|
||||
this.eventRepository.clientBroadcast('on_config_update');
|
||||
this.eventRepository.serverSend('ConfigUpdate', { oldConfig, newConfig });
|
||||
this.websocketRepository.clientBroadcast('on_config_update');
|
||||
this.websocketRepository.serverSend('ConfigUpdate', { oldConfig, newConfig });
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'ConfigValidate', priority: -100 })
|
||||
@@ -131,7 +131,7 @@ export class NotificationService extends BaseService {
|
||||
|
||||
@OnEvent({ name: 'AssetHide' })
|
||||
onAssetHide({ assetId, userId }: ArgOf<'AssetHide'>) {
|
||||
this.eventRepository.clientSend('on_asset_hidden', userId, assetId);
|
||||
this.websocketRepository.clientSend('on_asset_hidden', userId, assetId);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'AssetShow' })
|
||||
@@ -141,17 +141,17 @@ export class NotificationService extends BaseService {
|
||||
|
||||
@OnEvent({ name: 'AssetTrash' })
|
||||
onAssetTrash({ assetId, userId }: ArgOf<'AssetTrash'>) {
|
||||
this.eventRepository.clientSend('on_asset_trash', userId, [assetId]);
|
||||
this.websocketRepository.clientSend('on_asset_trash', userId, [assetId]);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'AssetDelete' })
|
||||
onAssetDelete({ assetId, userId }: ArgOf<'AssetDelete'>) {
|
||||
this.eventRepository.clientSend('on_asset_delete', userId, assetId);
|
||||
this.websocketRepository.clientSend('on_asset_delete', userId, assetId);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'AssetTrashAll' })
|
||||
onAssetsTrash({ assetIds, userId }: ArgOf<'AssetTrashAll'>) {
|
||||
this.eventRepository.clientSend('on_asset_trash', userId, assetIds);
|
||||
this.websocketRepository.clientSend('on_asset_trash', userId, assetIds);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'AssetMetadataExtracted' })
|
||||
@@ -162,7 +162,7 @@ export class NotificationService extends BaseService {
|
||||
|
||||
const [asset] = await this.assetRepository.getByIdsWithAllRelationsButStacks([assetId]);
|
||||
if (asset) {
|
||||
this.eventRepository.clientSend(
|
||||
this.websocketRepository.clientSend(
|
||||
'on_asset_update',
|
||||
userId,
|
||||
mapAsset(asset, { auth: { user: { id: userId } } as AuthDto }),
|
||||
@@ -172,27 +172,27 @@ export class NotificationService extends BaseService {
|
||||
|
||||
@OnEvent({ name: 'AssetRestoreAll' })
|
||||
onAssetsRestore({ assetIds, userId }: ArgOf<'AssetRestoreAll'>) {
|
||||
this.eventRepository.clientSend('on_asset_restore', userId, assetIds);
|
||||
this.websocketRepository.clientSend('on_asset_restore', userId, assetIds);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'StackCreate' })
|
||||
onStackCreate({ userId }: ArgOf<'StackCreate'>) {
|
||||
this.eventRepository.clientSend('on_asset_stack_update', userId);
|
||||
this.websocketRepository.clientSend('on_asset_stack_update', userId);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'StackUpdate' })
|
||||
onStackUpdate({ userId }: ArgOf<'StackUpdate'>) {
|
||||
this.eventRepository.clientSend('on_asset_stack_update', userId);
|
||||
this.websocketRepository.clientSend('on_asset_stack_update', userId);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'StackDelete' })
|
||||
onStackDelete({ userId }: ArgOf<'StackDelete'>) {
|
||||
this.eventRepository.clientSend('on_asset_stack_update', userId);
|
||||
this.websocketRepository.clientSend('on_asset_stack_update', userId);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'StackDeleteAll' })
|
||||
onStacksDelete({ userId }: ArgOf<'StackDeleteAll'>) {
|
||||
this.eventRepository.clientSend('on_asset_stack_update', userId);
|
||||
this.websocketRepository.clientSend('on_asset_stack_update', userId);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'UserSignup' })
|
||||
@@ -204,7 +204,7 @@ export class NotificationService extends BaseService {
|
||||
|
||||
@OnEvent({ name: 'UserDelete' })
|
||||
onUserDelete({ id }: ArgOf<'UserDelete'>) {
|
||||
this.eventRepository.clientBroadcast('on_user_delete', id);
|
||||
this.websocketRepository.clientBroadcast('on_user_delete', id);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'AlbumUpdate' })
|
||||
@@ -224,7 +224,7 @@ export class NotificationService extends BaseService {
|
||||
@OnEvent({ name: 'SessionDelete' })
|
||||
onSessionDelete({ sessionId }: ArgOf<'SessionDelete'>) {
|
||||
// after the response is sent
|
||||
setTimeout(() => this.eventRepository.clientSend('on_session_delete', sessionId, sessionId), 500);
|
||||
setTimeout(() => this.websocketRepository.clientSend('on_session_delete', sessionId, sessionId), 500);
|
||||
}
|
||||
|
||||
async sendTestEmail(id: string, dto: SystemConfigSmtpDto, tempTemplate?: string) {
|
||||
@@ -464,6 +464,6 @@ export class NotificationService extends BaseService {
|
||||
data: JSON.stringify({ albumId: album.id }),
|
||||
});
|
||||
|
||||
this.eventRepository.clientSend('on_notification', userId, mapNotification(item));
|
||||
this.websocketRepository.clientSend('on_notification', userId, mapNotification(item));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ describe(VersionService.name, () => {
|
||||
await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Success);
|
||||
expect(mocks.systemMetadata.set).toHaveBeenCalled();
|
||||
expect(mocks.logger.log).toHaveBeenCalled();
|
||||
expect(mocks.event.clientBroadcast).toHaveBeenCalled();
|
||||
expect(mocks.websocket.clientBroadcast).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not notify if the version is equal', async () => {
|
||||
@@ -118,14 +118,14 @@ describe(VersionService.name, () => {
|
||||
checkedAt: expect.any(String),
|
||||
releaseVersion: serverVersion.toString(),
|
||||
});
|
||||
expect(mocks.event.clientBroadcast).not.toHaveBeenCalled();
|
||||
expect(mocks.websocket.clientBroadcast).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle a github error', async () => {
|
||||
mocks.serverInfo.getGitHubRelease.mockRejectedValue(new Error('GitHub is down'));
|
||||
await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Failed);
|
||||
expect(mocks.systemMetadata.set).not.toHaveBeenCalled();
|
||||
expect(mocks.event.clientBroadcast).not.toHaveBeenCalled();
|
||||
expect(mocks.websocket.clientBroadcast).not.toHaveBeenCalled();
|
||||
expect(mocks.logger.warn).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -133,15 +133,15 @@ describe(VersionService.name, () => {
|
||||
describe('onWebsocketConnectionEvent', () => {
|
||||
it('should send on_server_version client event', async () => {
|
||||
await sut.onWebsocketConnection({ userId: '42' });
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer));
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer));
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should also send a new release notification', async () => {
|
||||
mocks.systemMetadata.get.mockResolvedValue({ checkedAt: '2024-01-01', releaseVersion: 'v1.42.0' });
|
||||
await sut.onWebsocketConnection({ userId: '42' });
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer));
|
||||
expect(mocks.event.clientSend).toHaveBeenCalledWith('on_new_release', '42', expect.any(Object));
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer));
|
||||
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_new_release', '42', expect.any(Object));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,7 +92,7 @@ export class VersionService extends BaseService {
|
||||
|
||||
if (semver.gt(releaseVersion, serverVersion)) {
|
||||
this.logger.log(`Found ${releaseVersion}, released at ${new Date(publishedAt).toLocaleString()}`);
|
||||
this.eventRepository.clientBroadcast('on_new_release', asNotification(metadata));
|
||||
this.websocketRepository.clientBroadcast('on_new_release', asNotification(metadata));
|
||||
}
|
||||
} catch (error: Error | any) {
|
||||
this.logger.warn(`Unable to run version check: ${error}\n${error?.stack}`);
|
||||
@@ -104,10 +104,10 @@ export class VersionService extends BaseService {
|
||||
|
||||
@OnEvent({ name: 'WebsocketConnect' })
|
||||
async onWebsocketConnection({ userId }: ArgOf<'WebsocketConnect'>) {
|
||||
this.eventRepository.clientSend('on_server_version', userId, serverVersion);
|
||||
this.websocketRepository.clientSend('on_server_version', userId, serverVersion);
|
||||
const metadata = await this.systemMetadataRepository.get(SystemMetadataKey.VersionCheckState);
|
||||
if (metadata) {
|
||||
this.eventRepository.clientSend('on_new_release', userId, asNotification(metadata));
|
||||
this.websocketRepository.clientSend('on_new_release', userId, asNotification(metadata));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user