mirror of
https://github.com/immich-app/immich.git
synced 2024-11-28 09:33:27 +02:00
refactor(server): client events (#13062)
This commit is contained in:
parent
47821cda35
commit
dfc2d5002b
@ -62,36 +62,20 @@ export type EmitHandler<T extends EmitEvent> = (...args: ArgsOf<T>) => Promise<v
|
||||
export type ArgOf<T extends EmitEvent> = EventMap[T][0];
|
||||
export type ArgsOf<T extends EmitEvent> = EventMap[T];
|
||||
|
||||
export enum ClientEvent {
|
||||
UPLOAD_SUCCESS = 'on_upload_success',
|
||||
USER_DELETE = 'on_user_delete',
|
||||
ASSET_DELETE = 'on_asset_delete',
|
||||
ASSET_TRASH = 'on_asset_trash',
|
||||
ASSET_UPDATE = 'on_asset_update',
|
||||
ASSET_HIDDEN = 'on_asset_hidden',
|
||||
ASSET_RESTORE = 'on_asset_restore',
|
||||
ASSET_STACK_UPDATE = 'on_asset_stack_update',
|
||||
PERSON_THUMBNAIL = 'on_person_thumbnail',
|
||||
SERVER_VERSION = 'on_server_version',
|
||||
CONFIG_UPDATE = 'on_config_update',
|
||||
NEW_RELEASE = 'on_new_release',
|
||||
SESSION_DELETE = 'on_session_delete',
|
||||
}
|
||||
|
||||
export interface ClientEventMap {
|
||||
[ClientEvent.UPLOAD_SUCCESS]: AssetResponseDto;
|
||||
[ClientEvent.USER_DELETE]: string;
|
||||
[ClientEvent.ASSET_DELETE]: string;
|
||||
[ClientEvent.ASSET_TRASH]: string[];
|
||||
[ClientEvent.ASSET_UPDATE]: AssetResponseDto;
|
||||
[ClientEvent.ASSET_HIDDEN]: string;
|
||||
[ClientEvent.ASSET_RESTORE]: string[];
|
||||
[ClientEvent.ASSET_STACK_UPDATE]: string[];
|
||||
[ClientEvent.PERSON_THUMBNAIL]: string;
|
||||
[ClientEvent.SERVER_VERSION]: ServerVersionResponseDto;
|
||||
[ClientEvent.CONFIG_UPDATE]: Record<string, never>;
|
||||
[ClientEvent.NEW_RELEASE]: ReleaseNotification;
|
||||
[ClientEvent.SESSION_DELETE]: string;
|
||||
on_upload_success: [AssetResponseDto];
|
||||
on_user_delete: [string];
|
||||
on_asset_delete: [string];
|
||||
on_asset_trash: [string[]];
|
||||
on_asset_update: [AssetResponseDto];
|
||||
on_asset_hidden: [string];
|
||||
on_asset_restore: [string[]];
|
||||
on_asset_stack_update: string[];
|
||||
on_person_thumbnail: [string];
|
||||
on_server_version: [ServerVersionResponseDto];
|
||||
on_config_update: [];
|
||||
on_new_release: [ReleaseNotification];
|
||||
on_session_delete: [string];
|
||||
}
|
||||
|
||||
export type EventItem<T extends EmitEvent> = {
|
||||
@ -107,11 +91,11 @@ export interface IEventRepository {
|
||||
/**
|
||||
* Send to connected clients for a specific user
|
||||
*/
|
||||
clientSend<E extends keyof ClientEventMap>(event: E, room: string, data: ClientEventMap[E]): void;
|
||||
clientSend<E extends keyof ClientEventMap>(event: E, room: string, ...data: ClientEventMap[E]): void;
|
||||
/**
|
||||
* Send to all connected clients
|
||||
*/
|
||||
clientBroadcast<E extends keyof ClientEventMap>(event: E, data: ClientEventMap[E]): void;
|
||||
clientBroadcast<E extends keyof ClientEventMap>(event: E, ...data: ClientEventMap[E]): void;
|
||||
/**
|
||||
* Send to all connected servers
|
||||
*/
|
||||
|
@ -106,12 +106,12 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect
|
||||
}
|
||||
}
|
||||
|
||||
clientSend<E extends keyof ClientEventMap>(event: E, room: string, data: ClientEventMap[E]) {
|
||||
this.server?.to(room).emit(event, data);
|
||||
clientSend<T extends keyof ClientEventMap>(event: T, room: string, ...data: ClientEventMap[T]) {
|
||||
this.server?.to(room).emit(event, ...data);
|
||||
}
|
||||
|
||||
clientBroadcast<E extends keyof ClientEventMap>(event: E, data: ClientEventMap[E]) {
|
||||
this.server?.emit(event, data);
|
||||
clientBroadcast<T extends keyof ClientEventMap>(event: T, ...data: ClientEventMap[T]) {
|
||||
this.server?.emit(event, ...data);
|
||||
}
|
||||
|
||||
serverSend<T extends ServerEvents>(event: T, ...args: ArgsOf<T>): void {
|
||||
|
@ -6,7 +6,7 @@ import { mapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AllJobStatusResponseDto, JobCommandDto, JobCreateDto, JobStatusDto } from 'src/dtos/job.dto';
|
||||
import { AssetType, ManualJobName } from 'src/enum';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { ArgOf, ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
|
||||
import { ArgOf, IEventRepository } from 'src/interfaces/event.interface';
|
||||
import {
|
||||
ConcurrentQueueName,
|
||||
IJobRepository,
|
||||
@ -279,7 +279,7 @@ export class JobService {
|
||||
if (item.data.source === 'sidecar-write') {
|
||||
const [asset] = await this.assetRepository.getByIdsWithAllRelations([item.data.id]);
|
||||
if (asset) {
|
||||
this.eventRepository.clientSend(ClientEvent.ASSET_UPDATE, asset.ownerId, mapAsset(asset));
|
||||
this.eventRepository.clientSend('on_asset_update', asset.ownerId, mapAsset(asset));
|
||||
}
|
||||
}
|
||||
await this.jobRepository.queue({ name: JobName.LINK_LIVE_PHOTOS, data: item.data });
|
||||
@ -302,7 +302,7 @@ export class JobService {
|
||||
const { id } = item.data;
|
||||
const person = await this.personRepository.getById(id);
|
||||
if (person) {
|
||||
this.eventRepository.clientSend(ClientEvent.PERSON_THUMBNAIL, person.ownerId, person.id);
|
||||
this.eventRepository.clientSend('on_person_thumbnail', person.ownerId, person.id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -331,7 +331,7 @@ export class JobService {
|
||||
|
||||
await this.jobRepository.queueAll(jobs);
|
||||
if (asset.isVisible) {
|
||||
this.eventRepository.clientSend(ClientEvent.UPLOAD_SUCCESS, asset.ownerId, mapAsset(asset));
|
||||
this.eventRepository.clientSend('on_upload_success', asset.ownerId, mapAsset(asset));
|
||||
}
|
||||
|
||||
break;
|
||||
@ -345,7 +345,7 @@ export class JobService {
|
||||
}
|
||||
|
||||
case JobName.USER_DELETION: {
|
||||
this.eventRepository.clientBroadcast(ClientEvent.USER_DELETE, item.data.id);
|
||||
this.eventRepository.clientBroadcast('on_user_delete', item.data.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||
import { AssetFileType, UserMetadataKey } from 'src/enum';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
|
||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { EmailTemplate, INotificationRepository } from 'src/interfaces/notification.interface';
|
||||
@ -104,7 +104,7 @@ describe(NotificationService.name, () => {
|
||||
it('should emit client and server events', () => {
|
||||
const update = { newConfig: defaults };
|
||||
expect(sut.onConfigUpdate(update)).toBeUndefined();
|
||||
expect(eventMock.clientBroadcast).toHaveBeenCalledWith(ClientEvent.CONFIG_UPDATE, {});
|
||||
expect(eventMock.clientBroadcast).toHaveBeenCalledWith('on_config_update');
|
||||
expect(eventMock.serverSend).toHaveBeenCalledWith('config.update', update);
|
||||
});
|
||||
});
|
||||
@ -236,28 +236,28 @@ describe(NotificationService.name, () => {
|
||||
describe('onStackCreate', () => {
|
||||
it('should send connected clients an event', () => {
|
||||
sut.onStackCreate({ stackId: 'stack-id', userId: 'user-id' });
|
||||
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id', []);
|
||||
expect(eventMock.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(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id', []);
|
||||
expect(eventMock.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(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id', []);
|
||||
expect(eventMock.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(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id', []);
|
||||
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto';
|
||||
import { AlbumEntity } from 'src/entities/album.entity';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { ArgOf, ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
|
||||
import { ArgOf, IEventRepository } from 'src/interfaces/event.interface';
|
||||
import {
|
||||
IEmailJob,
|
||||
IJobRepository,
|
||||
@ -45,7 +45,7 @@ export class NotificationService {
|
||||
|
||||
@OnEvent({ name: 'config.update' })
|
||||
onConfigUpdate({ oldConfig, newConfig }: ArgOf<'config.update'>) {
|
||||
this.eventRepository.clientBroadcast(ClientEvent.CONFIG_UPDATE, {});
|
||||
this.eventRepository.clientBroadcast('on_config_update');
|
||||
this.eventRepository.serverSend('config.update', { oldConfig, newConfig });
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ export class NotificationService {
|
||||
|
||||
@OnEvent({ name: 'asset.hide' })
|
||||
onAssetHide({ assetId, userId }: ArgOf<'asset.hide'>) {
|
||||
this.eventRepository.clientSend(ClientEvent.ASSET_HIDDEN, userId, assetId);
|
||||
this.eventRepository.clientSend('on_asset_hidden', userId, assetId);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'asset.show' })
|
||||
@ -76,42 +76,42 @@ export class NotificationService {
|
||||
|
||||
@OnEvent({ name: 'asset.trash' })
|
||||
onAssetTrash({ assetId, userId }: ArgOf<'asset.trash'>) {
|
||||
this.eventRepository.clientSend(ClientEvent.ASSET_TRASH, userId, [assetId]);
|
||||
this.eventRepository.clientSend('on_asset_trash', userId, [assetId]);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'asset.delete' })
|
||||
onAssetDelete({ assetId, userId }: ArgOf<'asset.delete'>) {
|
||||
this.eventRepository.clientSend(ClientEvent.ASSET_DELETE, userId, assetId);
|
||||
this.eventRepository.clientSend('on_asset_delete', userId, assetId);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'assets.trash' })
|
||||
onAssetsTrash({ assetIds, userId }: ArgOf<'assets.trash'>) {
|
||||
this.eventRepository.clientSend(ClientEvent.ASSET_TRASH, userId, assetIds);
|
||||
this.eventRepository.clientSend('on_asset_trash', userId, assetIds);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'assets.restore' })
|
||||
onAssetsRestore({ assetIds, userId }: ArgOf<'assets.restore'>) {
|
||||
this.eventRepository.clientSend(ClientEvent.ASSET_RESTORE, userId, assetIds);
|
||||
this.eventRepository.clientSend('on_asset_restore', userId, assetIds);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'stack.create' })
|
||||
onStackCreate({ userId }: ArgOf<'stack.create'>) {
|
||||
this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, userId, []);
|
||||
this.eventRepository.clientSend('on_asset_stack_update', userId);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'stack.update' })
|
||||
onStackUpdate({ userId }: ArgOf<'stack.update'>) {
|
||||
this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, userId, []);
|
||||
this.eventRepository.clientSend('on_asset_stack_update', userId);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'stack.delete' })
|
||||
onStackDelete({ userId }: ArgOf<'stack.delete'>) {
|
||||
this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, userId, []);
|
||||
this.eventRepository.clientSend('on_asset_stack_update', userId);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'stacks.delete' })
|
||||
onStacksDelete({ userId }: ArgOf<'stacks.delete'>) {
|
||||
this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, userId, []);
|
||||
this.eventRepository.clientSend('on_asset_stack_update', userId);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'user.signup' })
|
||||
@ -134,7 +134,7 @@ export class NotificationService {
|
||||
@OnEvent({ name: 'session.delete' })
|
||||
onSessionDelete({ sessionId }: ArgOf<'session.delete'>) {
|
||||
// after the response is sent
|
||||
setTimeout(() => this.eventRepository.clientSend(ClientEvent.SESSION_DELETE, sessionId, sessionId), 500);
|
||||
setTimeout(() => this.eventRepository.clientSend('on_session_delete', sessionId, sessionId), 500);
|
||||
}
|
||||
|
||||
async sendTestEmail(id: string, dto: SystemConfigSmtpDto) {
|
||||
|
@ -7,7 +7,7 @@ import { OnEvent } from 'src/decorators';
|
||||
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
|
||||
import { VersionCheckMetadata } from 'src/entities/system-metadata.entity';
|
||||
import { SystemMetadataKey } from 'src/enum';
|
||||
import { ArgOf, ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
|
||||
import { ArgOf, IEventRepository } from 'src/interfaces/event.interface';
|
||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
|
||||
@ -80,7 +80,7 @@ export class VersionService {
|
||||
|
||||
if (semver.gt(releaseVersion, serverVersion)) {
|
||||
this.logger.log(`Found ${releaseVersion}, released at ${new Date(publishedAt).toLocaleString()}`);
|
||||
this.eventRepository.clientBroadcast(ClientEvent.NEW_RELEASE, asNotification(metadata));
|
||||
this.eventRepository.clientBroadcast('on_new_release', asNotification(metadata));
|
||||
}
|
||||
} catch (error: Error | any) {
|
||||
this.logger.warn(`Unable to run version check: ${error}`, error?.stack);
|
||||
@ -92,10 +92,10 @@ export class VersionService {
|
||||
|
||||
@OnEvent({ name: 'websocket.connect' })
|
||||
async onWebsocketConnection({ userId }: ArgOf<'websocket.connect'>) {
|
||||
this.eventRepository.clientSend(ClientEvent.SERVER_VERSION, userId, serverVersion);
|
||||
this.eventRepository.clientSend('on_server_version', userId, serverVersion);
|
||||
const metadata = await this.systemMetadataRepository.get(SystemMetadataKey.VERSION_CHECK_STATE);
|
||||
if (metadata) {
|
||||
this.eventRepository.clientSend(ClientEvent.NEW_RELEASE, userId, asNotification(metadata));
|
||||
this.eventRepository.clientSend('on_new_release', userId, asNotification(metadata));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ export const newEventRepositoryMock = (): Mocked<IEventRepository> => {
|
||||
return {
|
||||
on: vitest.fn() as any,
|
||||
emit: vitest.fn() as any,
|
||||
clientSend: vitest.fn(),
|
||||
clientBroadcast: vitest.fn(),
|
||||
clientSend: vitest.fn() as any,
|
||||
clientBroadcast: vitest.fn() as any,
|
||||
serverSend: vitest.fn(),
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user