diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 0096cc6c26761..2d6d7878dcd8a 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -13,7 +13,6 @@ import { IWorker } from 'src/constants'; import { controllers } from 'src/controllers'; import { entities } from 'src/entities'; import { ImmichWorker } from 'src/enum'; -import { IEventRepository } from 'src/interfaces/event.interface'; import { IJobRepository } from 'src/interfaces/job.interface'; import { AuthGuard } from 'src/middleware/auth.guard'; import { ErrorInterceptor } from 'src/middleware/error.interceptor'; @@ -22,9 +21,11 @@ import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter'; import { LoggingInterceptor } from 'src/middleware/logging.interceptor'; import { providers, repositories } from 'src/repositories'; import { ConfigRepository } from 'src/repositories/config.repository'; +import { EventRepository } from 'src/repositories/event.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { teardownTelemetry, TelemetryRepository } from 'src/repositories/telemetry.repository'; import { services } from 'src/services'; +import { AuthService } from 'src/services/auth.service'; import { CliService } from 'src/services/cli.service'; import { DatabaseService } from 'src/services/database.service'; @@ -78,9 +79,10 @@ class BaseModule implements OnModuleInit, OnModuleDestroy { constructor( @Inject(IWorker) private worker: ImmichWorker, logger: LoggingRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, + private eventRepository: EventRepository, @Inject(IJobRepository) private jobRepository: IJobRepository, private telemetryRepository: TelemetryRepository, + private authService: AuthService, ) { logger.setAppName(this.worker); } @@ -93,6 +95,14 @@ class BaseModule implements OnModuleInit, OnModuleDestroy { this.jobRepository.startWorkers(); } + this.eventRepository.setAuthFn(async (client) => + this.authService.authenticate({ + headers: client.request.headers, + queryParams: {}, + metadata: { adminRoute: false, sharedLinkRoute: false, uri: '/api/socket.io' }, + }), + ); + this.eventRepository.setup({ services }); await this.eventRepository.emit('app.bootstrap'); } diff --git a/server/src/decorators.ts b/server/src/decorators.ts index bb037ee097a80..7aab2e248aca1 100644 --- a/server/src/decorators.ts +++ b/server/src/decorators.ts @@ -3,8 +3,8 @@ import { ApiExtension, ApiOperation, ApiProperty, ApiTags } from '@nestjs/swagge import _ from 'lodash'; import { ADDED_IN_PREFIX, DEPRECATED_IN_PREFIX, LIFECYCLE_EXTENSION } from 'src/constants'; import { ImmichWorker, MetadataKey } from 'src/enum'; -import { EmitEvent } from 'src/interfaces/event.interface'; import { JobName, QueueName } from 'src/interfaces/job.interface'; +import { EmitEvent } from 'src/repositories/event.repository'; import { setUnion } from 'src/utils/set'; // PostgreSQL uses a 16-bit integer to indicate the number of bound parameters. This means that the diff --git a/server/src/enum.ts b/server/src/enum.ts index 332ae50ee8d0e..b887fbace3bb3 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -391,3 +391,10 @@ export enum DatabaseExtension { VECTOR = 'vector', VECTORS = 'vectors', } + +export enum BootstrapEventPriority { + // Database service should be initialized before anything else, most other services need database access + DatabaseService = -200, + // Initialise config after other bootstrap services, stop other services from using config on bootstrap + SystemConfig = 100, +} diff --git a/server/src/interfaces/event.interface.ts b/server/src/interfaces/event.interface.ts deleted file mode 100644 index 9a9e23cca0018..0000000000000 --- a/server/src/interfaces/event.interface.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { ClassConstructor } from 'class-transformer'; -import { SystemConfig } from 'src/config'; -import { AssetResponseDto } from 'src/dtos/asset-response.dto'; -import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; -import { JobItem, QueueName } from 'src/interfaces/job.interface'; - -export const IEventRepository = 'IEventRepository'; - -type EventMap = { - // app events - 'app.bootstrap': []; - 'app.shutdown': []; - - 'config.init': [{ newConfig: SystemConfig }]; - // config events - 'config.update': [ - { - newConfig: SystemConfig; - oldConfig: SystemConfig; - }, - ]; - 'config.validate': [{ newConfig: SystemConfig; oldConfig: SystemConfig }]; - - // album events - 'album.update': [{ id: string; recipientIds: string[] }]; - 'album.invite': [{ id: string; userId: string }]; - - // asset events - 'asset.tag': [{ assetId: string }]; - 'asset.untag': [{ assetId: string }]; - 'asset.hide': [{ assetId: string; userId: string }]; - 'asset.show': [{ assetId: string; userId: string }]; - 'asset.trash': [{ assetId: string; userId: string }]; - 'asset.delete': [{ assetId: string; userId: string }]; - - // asset bulk events - 'assets.trash': [{ assetIds: string[]; userId: string }]; - 'assets.delete': [{ assetIds: string[]; userId: string }]; - 'assets.restore': [{ assetIds: string[]; userId: string }]; - - 'job.start': [QueueName, JobItem]; - - // session events - 'session.delete': [{ sessionId: string }]; - - // stack events - 'stack.create': [{ stackId: string; userId: string }]; - 'stack.update': [{ stackId: string; userId: string }]; - 'stack.delete': [{ stackId: string; userId: string }]; - - // stack bulk events - 'stacks.delete': [{ stackIds: string[]; userId: string }]; - - // user events - 'user.signup': [{ notify: boolean; id: string; tempPassword?: string }]; - - // websocket events - 'websocket.connect': [{ userId: string }]; -}; - -export const serverEvents = ['config.update'] as const; -export type ServerEvents = (typeof serverEvents)[number]; - -export type EmitEvent = keyof EventMap; -export type EmitHandler = (...args: ArgsOf) => Promise | void; -export type ArgOf = EventMap[T][0]; -export type ArgsOf = EventMap[T]; - -export interface ClientEventMap { - 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 = { - event: T; - handler: EmitHandler; - server: boolean; -}; - -export enum BootstrapEventPriority { - // Database service should be initialized before anything else, most other services need database access - DatabaseService = -200, - // Initialise config after other bootstrap services, stop other services from using config on bootstrap - SystemConfig = 100, -} - -export interface IEventRepository { - setup(options: { services: ClassConstructor[] }): void; - emit(event: T, ...args: ArgsOf): Promise; - - /** - * Send to connected clients for a specific user - */ - clientSend(event: E, room: string, ...data: ClientEventMap[E]): void; - /** - * Send to all connected clients - */ - clientBroadcast(event: E, ...data: ClientEventMap[E]): void; - /** - * Send to all connected servers - */ - serverSend(event: T, ...args: ArgsOf): void; -} diff --git a/server/src/interfaces/machine-learning.interface.ts b/server/src/interfaces/machine-learning.interface.ts deleted file mode 100644 index 934091ef8e3ff..0000000000000 --- a/server/src/interfaces/machine-learning.interface.ts +++ /dev/null @@ -1,57 +0,0 @@ -export const IMachineLearningRepository = 'IMachineLearningRepository'; - -export interface BoundingBox { - x1: number; - y1: number; - x2: number; - y2: number; -} - -export enum ModelTask { - FACIAL_RECOGNITION = 'facial-recognition', - SEARCH = 'clip', -} - -export enum ModelType { - DETECTION = 'detection', - PIPELINE = 'pipeline', - RECOGNITION = 'recognition', - TEXTUAL = 'textual', - VISUAL = 'visual', -} - -export type ModelPayload = { imagePath: string } | { text: string }; - -type ModelOptions = { modelName: string }; - -export type FaceDetectionOptions = ModelOptions & { minScore: number }; - -type VisualResponse = { imageHeight: number; imageWidth: number }; -export type ClipVisualRequest = { [ModelTask.SEARCH]: { [ModelType.VISUAL]: ModelOptions } }; -export type ClipVisualResponse = { [ModelTask.SEARCH]: string } & VisualResponse; - -export type ClipTextualRequest = { [ModelTask.SEARCH]: { [ModelType.TEXTUAL]: ModelOptions } }; -export type ClipTextualResponse = { [ModelTask.SEARCH]: string }; - -export type FacialRecognitionRequest = { - [ModelTask.FACIAL_RECOGNITION]: { - [ModelType.DETECTION]: ModelOptions & { options: { minScore: number } }; - [ModelType.RECOGNITION]: ModelOptions; - }; -}; - -export interface Face { - boundingBox: BoundingBox; - embedding: string; - score: number; -} - -export type FacialRecognitionResponse = { [ModelTask.FACIAL_RECOGNITION]: Face[] } & VisualResponse; -export type DetectedFaces = { faces: Face[] } & VisualResponse; -export type MachineLearningRequest = ClipVisualRequest | ClipTextualRequest | FacialRecognitionRequest; - -export interface IMachineLearningRepository { - encodeImage(urls: string[], imagePath: string, config: ModelOptions): Promise; - encodeText(urls: string[], text: string, config: ModelOptions): Promise; - detectFaces(urls: string[], imagePath: string, config: FaceDetectionOptions): Promise; -} diff --git a/server/src/repositories/event.repository.ts b/server/src/repositories/event.repository.ts index a443e0ed83d47..671b86f99c680 100644 --- a/server/src/repositories/event.repository.ts +++ b/server/src/repositories/event.repository.ts @@ -10,21 +10,15 @@ import { import { ClassConstructor } from 'class-transformer'; import _ from 'lodash'; import { Server, Socket } from 'socket.io'; +import { SystemConfig } from 'src/config'; import { EventConfig } from 'src/decorators'; +import { AssetResponseDto } from 'src/dtos/asset-response.dto'; +import { AuthDto } from 'src/dtos/auth.dto'; +import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; import { ImmichWorker, MetadataKey } from 'src/enum'; -import { - ArgsOf, - ClientEventMap, - EmitEvent, - EmitHandler, - EventItem, - IEventRepository, - serverEvents, - ServerEvents, -} from 'src/interfaces/event.interface'; +import { JobItem, QueueName } from 'src/interfaces/job.interface'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; -import { AuthService } from 'src/services/auth.service'; import { handlePromiseError } from 'src/utils/misc'; type EmitHandlers = Partial<{ [T in EmitEvent]: Array> }>; @@ -37,14 +31,99 @@ type Item = { label: string; }; +type EventMap = { + // app events + 'app.bootstrap': []; + 'app.shutdown': []; + + 'config.init': [{ newConfig: SystemConfig }]; + // config events + 'config.update': [ + { + newConfig: SystemConfig; + oldConfig: SystemConfig; + }, + ]; + 'config.validate': [{ newConfig: SystemConfig; oldConfig: SystemConfig }]; + + // album events + 'album.update': [{ id: string; recipientIds: string[] }]; + 'album.invite': [{ id: string; userId: string }]; + + // asset events + 'asset.tag': [{ assetId: string }]; + 'asset.untag': [{ assetId: string }]; + 'asset.hide': [{ assetId: string; userId: string }]; + 'asset.show': [{ assetId: string; userId: string }]; + 'asset.trash': [{ assetId: string; userId: string }]; + 'asset.delete': [{ assetId: string; userId: string }]; + + // asset bulk events + 'assets.trash': [{ assetIds: string[]; userId: string }]; + 'assets.delete': [{ assetIds: string[]; userId: string }]; + 'assets.restore': [{ assetIds: string[]; userId: string }]; + + 'job.start': [QueueName, JobItem]; + + // session events + 'session.delete': [{ sessionId: string }]; + + // stack events + 'stack.create': [{ stackId: string; userId: string }]; + 'stack.update': [{ stackId: string; userId: string }]; + 'stack.delete': [{ stackId: string; userId: string }]; + + // stack bulk events + 'stacks.delete': [{ stackIds: string[]; userId: string }]; + + // user events + 'user.signup': [{ notify: boolean; id: string; tempPassword?: string }]; + + // websocket events + 'websocket.connect': [{ userId: string }]; +}; + +export const serverEvents = ['config.update'] as const; +export type ServerEvents = (typeof serverEvents)[number]; + +export type EmitEvent = keyof EventMap; +export type EmitHandler = (...args: ArgsOf) => Promise | void; +export type ArgOf = EventMap[T][0]; +export type ArgsOf = EventMap[T]; + +export interface ClientEventMap { + 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 = { + event: T; + handler: EmitHandler; + server: boolean; +}; + +export type AuthFn = (client: Socket) => Promise; + @WebSocketGateway({ cors: true, path: '/api/socket.io', transports: ['websocket'], }) @Injectable() -export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, IEventRepository { +export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit { private emitHandlers: EmitHandlers = {}; + private authFn?: AuthFn; @WebSocketServer() private server?: Server; @@ -122,11 +201,7 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect async handleConnection(client: Socket) { try { this.logger.log(`Websocket Connect: ${client.id}`); - const auth = await this.moduleRef.get(AuthService).authenticate({ - headers: client.request.headers, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: '/api/socket.io' }, - }); + const auth = await this.authenticate(client); await client.join(auth.user.id); if (auth.session) { await client.join(auth.session.id); @@ -182,4 +257,16 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect this.logger.debug(`Server event: ${event} (send)`); this.server?.serverSideEmit(event, ...args); } + + setAuthFn(fn: (client: Socket) => Promise) { + this.authFn = fn; + } + + private async authenticate(client: Socket) { + if (!this.authFn) { + throw new Error('Auth function not set'); + } + + return this.authFn(client); + } } diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index d9ef84fc06a7d..3c96f4c89127e 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -1,6 +1,4 @@ -import { IEventRepository } from 'src/interfaces/event.interface'; import { IJobRepository } from 'src/interfaces/job.interface'; -import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { AccessRepository } from 'src/repositories/access.repository'; import { ActivityRepository } from 'src/repositories/activity.repository'; import { AlbumUserRepository } from 'src/repositories/album-user.repository'; @@ -53,8 +51,10 @@ export const repositories = [ CronRepository, CryptoRepository, DatabaseRepository, + EventRepository, LibraryRepository, LoggingRepository, + MachineLearningRepository, MapRepository, MediaRepository, MemoryRepository, @@ -80,8 +80,4 @@ export const repositories = [ VersionHistoryRepository, ]; -export const providers = [ - { provide: IEventRepository, useClass: EventRepository }, - { provide: IJobRepository, useClass: JobRepository }, - { provide: IMachineLearningRepository, useClass: MachineLearningRepository }, -]; +export const providers = [{ provide: IJobRepository, useClass: JobRepository }]; diff --git a/server/src/repositories/job.repository.ts b/server/src/repositories/job.repository.ts index 9a5bf20df6143..d6693f67f3256 100644 --- a/server/src/repositories/job.repository.ts +++ b/server/src/repositories/job.repository.ts @@ -1,12 +1,11 @@ import { getQueueToken } from '@nestjs/bullmq'; -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { ModuleRef, Reflector } from '@nestjs/core'; import { JobsOptions, Queue, Worker } from 'bullmq'; import { ClassConstructor } from 'class-transformer'; import { setTimeout } from 'node:timers/promises'; import { JobConfig } from 'src/decorators'; import { MetadataKey } from 'src/enum'; -import { IEventRepository } from 'src/interfaces/event.interface'; import { IEntityJob, IJobRepository, @@ -20,6 +19,7 @@ import { QueueStatus, } from 'src/interfaces/job.interface'; import { ConfigRepository } from 'src/repositories/config.repository'; +import { EventRepository } from 'src/repositories/event.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { getKeyByValue, getMethodNames, ImmichStartupError } from 'src/utils/misc'; @@ -38,7 +38,7 @@ export class JobRepository implements IJobRepository { constructor( private moduleRef: ModuleRef, private configRepository: ConfigRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, + private eventRepository: EventRepository, private logger: LoggingRepository, ) { this.logger.setContext(JobRepository.name); diff --git a/server/src/repositories/machine-learning.repository.ts b/server/src/repositories/machine-learning.repository.ts index 6266314bfd8ab..8145bf315464e 100644 --- a/server/src/repositories/machine-learning.repository.ts +++ b/server/src/repositories/machine-learning.repository.ts @@ -1,21 +1,60 @@ import { Injectable } from '@nestjs/common'; import { readFile } from 'node:fs/promises'; import { CLIPConfig } from 'src/dtos/model-config.dto'; -import { - ClipTextualResponse, - ClipVisualResponse, - FaceDetectionOptions, - FacialRecognitionResponse, - IMachineLearningRepository, - MachineLearningRequest, - ModelPayload, - ModelTask, - ModelType, -} from 'src/interfaces/machine-learning.interface'; import { LoggingRepository } from 'src/repositories/logging.repository'; +export interface BoundingBox { + x1: number; + y1: number; + x2: number; + y2: number; +} + +export enum ModelTask { + FACIAL_RECOGNITION = 'facial-recognition', + SEARCH = 'clip', +} + +export enum ModelType { + DETECTION = 'detection', + PIPELINE = 'pipeline', + RECOGNITION = 'recognition', + TEXTUAL = 'textual', + VISUAL = 'visual', +} + +export type ModelPayload = { imagePath: string } | { text: string }; + +type ModelOptions = { modelName: string }; + +export type FaceDetectionOptions = ModelOptions & { minScore: number }; + +type VisualResponse = { imageHeight: number; imageWidth: number }; +export type ClipVisualRequest = { [ModelTask.SEARCH]: { [ModelType.VISUAL]: ModelOptions } }; +export type ClipVisualResponse = { [ModelTask.SEARCH]: string } & VisualResponse; + +export type ClipTextualRequest = { [ModelTask.SEARCH]: { [ModelType.TEXTUAL]: ModelOptions } }; +export type ClipTextualResponse = { [ModelTask.SEARCH]: string }; + +export type FacialRecognitionRequest = { + [ModelTask.FACIAL_RECOGNITION]: { + [ModelType.DETECTION]: ModelOptions & { options: { minScore: number } }; + [ModelType.RECOGNITION]: ModelOptions; + }; +}; + +export interface Face { + boundingBox: BoundingBox; + embedding: string; + score: number; +} + +export type FacialRecognitionResponse = { [ModelTask.FACIAL_RECOGNITION]: Face[] } & VisualResponse; +export type DetectedFaces = { faces: Face[] } & VisualResponse; +export type MachineLearningRequest = ClipVisualRequest | ClipTextualRequest | FacialRecognitionRequest; + @Injectable() -export class MachineLearningRepository implements IMachineLearningRepository { +export class MachineLearningRepository { constructor(private logger: LoggingRepository) { this.logger.setContext(MachineLearningRepository.name); } diff --git a/server/src/services/backup.service.ts b/server/src/services/backup.service.ts index d0a8ce69b674a..dee8113792f5a 100644 --- a/server/src/services/backup.service.ts +++ b/server/src/services/backup.service.ts @@ -4,9 +4,9 @@ import semver from 'semver'; import { StorageCore } from 'src/cores/storage.core'; import { OnEvent, OnJob } from 'src/decorators'; import { ImmichWorker, StorageFolder } from 'src/enum'; -import { ArgOf } from 'src/interfaces/event.interface'; import { JobName, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { DatabaseLock } from 'src/repositories/database.repository'; +import { ArgOf } from 'src/repositories/event.repository'; import { BaseService } from 'src/services/base.service'; import { handlePromiseError } from 'src/utils/misc'; diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index d0f00bd2ab367..259248e49b6f9 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -6,9 +6,7 @@ import { SALT_ROUNDS } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; import { Users } from 'src/db'; import { UserEntity } from 'src/entities/user.entity'; -import { IEventRepository } from 'src/interfaces/event.interface'; import { IJobRepository } from 'src/interfaces/job.interface'; -import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { AccessRepository } from 'src/repositories/access.repository'; import { ActivityRepository } from 'src/repositories/activity.repository'; import { AlbumUserRepository } from 'src/repositories/album-user.repository'; @@ -20,8 +18,10 @@ import { ConfigRepository } from 'src/repositories/config.repository'; import { CronRepository } from 'src/repositories/cron.repository'; import { CryptoRepository } from 'src/repositories/crypto.repository'; import { DatabaseRepository } from 'src/repositories/database.repository'; +import { EventRepository } from 'src/repositories/event.repository'; import { LibraryRepository } from 'src/repositories/library.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; +import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; import { MapRepository } from 'src/repositories/map.repository'; import { MediaRepository } from 'src/repositories/media.repository'; import { MemoryRepository } from 'src/repositories/memory.repository'; @@ -63,11 +63,11 @@ export class BaseService { protected cronRepository: CronRepository, protected cryptoRepository: CryptoRepository, protected databaseRepository: DatabaseRepository, - @Inject(IEventRepository) protected eventRepository: IEventRepository, + protected eventRepository: EventRepository, @Inject(IJobRepository) protected jobRepository: IJobRepository, protected keyRepository: ApiKeyRepository, protected libraryRepository: LibraryRepository, - @Inject(IMachineLearningRepository) protected machineLearningRepository: IMachineLearningRepository, + protected machineLearningRepository: MachineLearningRepository, protected mapRepository: MapRepository, protected mediaRepository: MediaRepository, protected memoryRepository: MemoryRepository, diff --git a/server/src/services/database.service.ts b/server/src/services/database.service.ts index eabb0b0091c8f..249b47c99cffb 100644 --- a/server/src/services/database.service.ts +++ b/server/src/services/database.service.ts @@ -2,8 +2,7 @@ import { Injectable } from '@nestjs/common'; import { Duration } from 'luxon'; import semver from 'semver'; import { OnEvent } from 'src/decorators'; -import { DatabaseExtension } from 'src/enum'; -import { BootstrapEventPriority } from 'src/interfaces/event.interface'; +import { BootstrapEventPriority, DatabaseExtension } from 'src/enum'; import { DatabaseLock, EXTENSION_NAMES, VectorExtension, VectorIndex } from 'src/repositories/database.repository'; import { BaseService } from 'src/services/base.service'; diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index c8ac8fc6bfb46..00d9a398fdc9c 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -4,7 +4,6 @@ import { OnEvent } from 'src/decorators'; import { mapAsset } from 'src/dtos/asset-response.dto'; import { AllJobStatusResponseDto, JobCommandDto, JobCreateDto, JobStatusDto } from 'src/dtos/job.dto'; import { AssetType, ImmichWorker, ManualJobName } from 'src/enum'; -import { ArgOf, ArgsOf } from 'src/interfaces/event.interface'; import { ConcurrentQueueName, JobCommand, @@ -14,6 +13,7 @@ import { QueueCleanType, QueueName, } from 'src/interfaces/job.interface'; +import { ArgOf, ArgsOf } from 'src/repositories/event.repository'; import { BaseService } from 'src/services/base.service'; const asJobItem = (dto: JobCreateDto): JobItem => { diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts index 5235b786e916d..fb748276565c6 100644 --- a/server/src/services/library.service.ts +++ b/server/src/services/library.service.ts @@ -17,9 +17,9 @@ import { import { AssetEntity } from 'src/entities/asset.entity'; import { LibraryEntity } from 'src/entities/library.entity'; import { AssetType, ImmichWorker } from 'src/enum'; -import { ArgOf } from 'src/interfaces/event.interface'; import { JobName, JobOf, JOBS_LIBRARY_PAGINATION_SIZE, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { DatabaseLock } from 'src/repositories/database.repository'; +import { ArgOf } from 'src/repositories/event.repository'; import { BaseService } from 'src/services/base.service'; import { mimeTypes } from 'src/utils/mime-types'; import { handlePromiseError } from 'src/utils/misc'; diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 53110a20e0160..33db5d3149450 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -14,10 +14,10 @@ import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetEntity } from 'src/entities/asset.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { AssetType, ExifOrientation, ImmichWorker, SourceType } from 'src/enum'; -import { ArgOf } from 'src/interfaces/event.interface'; import { JobName, JobOf, JOBS_ASSET_PAGINATION_SIZE, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { WithoutProperty } from 'src/repositories/asset.repository'; import { DatabaseLock } from 'src/repositories/database.repository'; +import { ArgOf } from 'src/repositories/event.repository'; import { ReverseGeocodeResult } from 'src/repositories/map.repository'; import { ImmichTags } from 'src/repositories/metadata.repository'; import { BaseService } from 'src/services/base.service'; diff --git a/server/src/services/notification.service.ts b/server/src/services/notification.service.ts index 85f72443d4baa..f9a2194088651 100644 --- a/server/src/services/notification.service.ts +++ b/server/src/services/notification.service.ts @@ -2,7 +2,6 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { OnEvent, OnJob } from 'src/decorators'; import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto'; import { AlbumEntity } from 'src/entities/album.entity'; -import { ArgOf } from 'src/interfaces/event.interface'; import { IEntityJob, INotifyAlbumUpdateJob, @@ -12,6 +11,7 @@ import { JobStatus, QueueName, } from 'src/interfaces/job.interface'; +import { ArgOf } from 'src/repositories/event.repository'; import { EmailImageAttachment, EmailTemplate } from 'src/repositories/notification.repository'; import { BaseService } from 'src/services/base.service'; import { getAssetFiles } from 'src/utils/asset.util'; diff --git a/server/src/services/person.service.spec.ts b/server/src/services/person.service.spec.ts index ec2417c793b58..8e1cff302fdc9 100644 --- a/server/src/services/person.service.spec.ts +++ b/server/src/services/person.service.spec.ts @@ -4,8 +4,8 @@ import { mapFaces, mapPerson, PersonResponseDto } from 'src/dtos/person.dto'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { CacheControl, Colorspace, ImageFormat, SourceType, SystemMetadataKey } from 'src/enum'; import { JobName, JobStatus } from 'src/interfaces/job.interface'; -import { DetectedFaces } from 'src/interfaces/machine-learning.interface'; import { WithoutProperty } from 'src/repositories/asset.repository'; +import { DetectedFaces } from 'src/repositories/machine-learning.repository'; import { FaceSearchResult } from 'src/repositories/search.repository'; import { PersonService } from 'src/services/person.service'; import { ImmichFileResponse } from 'src/utils/file'; diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index 2ae703afb3770..e9933b421cb16 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -40,8 +40,8 @@ import { JobStatus, QueueName, } from 'src/interfaces/job.interface'; -import { BoundingBox } from 'src/interfaces/machine-learning.interface'; import { WithoutProperty } from 'src/repositories/asset.repository'; +import { BoundingBox } from 'src/repositories/machine-learning.repository'; import { UpdateFacesData } from 'src/repositories/person.repository'; import { BaseService } from 'src/services/base.service'; import { CropOptions, ImageDimensions, InputDimensions } from 'src/types'; diff --git a/server/src/services/smart-info.service.ts b/server/src/services/smart-info.service.ts index ebf9e1d28794b..0a03c27a55949 100644 --- a/server/src/services/smart-info.service.ts +++ b/server/src/services/smart-info.service.ts @@ -2,10 +2,10 @@ import { Injectable } from '@nestjs/common'; import { SystemConfig } from 'src/config'; import { OnEvent, OnJob } from 'src/decorators'; import { ImmichWorker } from 'src/enum'; -import { ArgOf } from 'src/interfaces/event.interface'; import { JOBS_ASSET_PAGINATION_SIZE, JobName, JobOf, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { WithoutProperty } from 'src/repositories/asset.repository'; import { DatabaseLock } from 'src/repositories/database.repository'; +import { ArgOf } from 'src/repositories/event.repository'; import { BaseService } from 'src/services/base.service'; import { getAssetFiles } from 'src/utils/asset.util'; import { getCLIPModelInfo, isSmartSearchEnabled } from 'src/utils/misc'; diff --git a/server/src/services/storage-template.service.ts b/server/src/services/storage-template.service.ts index 6b0409de1de95..6fd139c10d1e4 100644 --- a/server/src/services/storage-template.service.ts +++ b/server/src/services/storage-template.service.ts @@ -8,9 +8,9 @@ import { OnEvent, OnJob } from 'src/decorators'; import { SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { AssetPathType, AssetType, StorageFolder } from 'src/enum'; -import { ArgOf } from 'src/interfaces/event.interface'; import { JobName, JobOf, JOBS_ASSET_PAGINATION_SIZE, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { DatabaseLock } from 'src/repositories/database.repository'; +import { ArgOf } from 'src/repositories/event.repository'; import { BaseService } from 'src/services/base.service'; import { getLivePhotoMotionFilename } from 'src/utils/file'; import { usePagination } from 'src/utils/pagination'; diff --git a/server/src/services/system-config.service.ts b/server/src/services/system-config.service.ts index b5ae42e0989d1..cc32ef0c341d6 100644 --- a/server/src/services/system-config.service.ts +++ b/server/src/services/system-config.service.ts @@ -4,7 +4,8 @@ import _ from 'lodash'; import { defaults } from 'src/config'; import { OnEvent } from 'src/decorators'; import { SystemConfigDto, mapConfig } from 'src/dtos/system-config.dto'; -import { ArgOf, BootstrapEventPriority } from 'src/interfaces/event.interface'; +import { BootstrapEventPriority } from 'src/enum'; +import { ArgOf } from 'src/repositories/event.repository'; import { BaseService } from 'src/services/base.service'; import { clearConfigCache } from 'src/utils/config'; import { toPlainObject } from 'src/utils/object'; diff --git a/server/src/services/version.service.ts b/server/src/services/version.service.ts index ee36d30041844..9679ac4b4b6c0 100644 --- a/server/src/services/version.service.ts +++ b/server/src/services/version.service.ts @@ -6,9 +6,9 @@ import { OnEvent, OnJob } from 'src/decorators'; import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; import { VersionCheckMetadata } from 'src/entities/system-metadata.entity'; import { ImmichEnvironment, SystemMetadataKey } from 'src/enum'; -import { ArgOf } from 'src/interfaces/event.interface'; import { JobName, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { DatabaseLock } from 'src/repositories/database.repository'; +import { ArgOf } from 'src/repositories/event.repository'; import { BaseService } from 'src/services/base.service'; const asNotification = ({ checkedAt, releaseVersion }: VersionCheckMetadata): ReleaseNotification => { diff --git a/server/src/utils/asset.util.ts b/server/src/utils/asset.util.ts index 703fe2c3d76c5..5183bb2164e50 100644 --- a/server/src/utils/asset.util.ts +++ b/server/src/utils/asset.util.ts @@ -5,11 +5,11 @@ import { UploadFieldName } from 'src/dtos/asset-media.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { AssetFileEntity } from 'src/entities/asset-files.entity'; import { AssetFileType, AssetType, Permission } from 'src/enum'; -import { IEventRepository } from 'src/interfaces/event.interface'; import { AuthRequest } from 'src/middleware/auth.guard'; import { ImmichFile } from 'src/middleware/file-upload.interceptor'; import { AccessRepository } from 'src/repositories/access.repository'; import { AssetRepository } from 'src/repositories/asset.repository'; +import { EventRepository } from 'src/repositories/event.repository'; import { PartnerRepository } from 'src/repositories/partner.repository'; import { UploadFile } from 'src/services/asset-media.service'; import { checkAccess } from 'src/utils/access'; @@ -139,7 +139,7 @@ export const getMyPartnerIds = async ({ userId, repository, timelineEnabled }: P return [...partnerIds]; }; -export type AssetHookRepositories = { asset: AssetRepository; event: IEventRepository }; +export type AssetHookRepositories = { asset: AssetRepository; event: EventRepository }; export const onBeforeLink = async ( { asset: assetRepository, event: eventRepository }: AssetHookRepositories, diff --git a/server/test/repositories/event.repository.mock.ts b/server/test/repositories/event.repository.mock.ts index a425ddef3a7bc..a253e93671d6a 100644 --- a/server/test/repositories/event.repository.mock.ts +++ b/server/test/repositories/event.repository.mock.ts @@ -1,12 +1,17 @@ -import { IEventRepository } from 'src/interfaces/event.interface'; +import { EventRepository } from 'src/repositories/event.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newEventRepositoryMock = (): Mocked => { +export const newEventRepositoryMock = (): Mocked> => { return { setup: vitest.fn(), emit: vitest.fn() as any, clientSend: vitest.fn() as any, clientBroadcast: vitest.fn() as any, serverSend: vitest.fn(), + afterInit: vitest.fn(), + handleConnection: vitest.fn(), + handleDisconnect: vitest.fn(), + setAuthFn: vitest.fn(), }; }; diff --git a/server/test/repositories/machine-learning.repository.mock.ts b/server/test/repositories/machine-learning.repository.mock.ts index 9dd1bdca29736..229e8f92eca19 100644 --- a/server/test/repositories/machine-learning.repository.mock.ts +++ b/server/test/repositories/machine-learning.repository.mock.ts @@ -1,7 +1,8 @@ -import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; +import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newMachineLearningRepositoryMock = (): Mocked => { +export const newMachineLearningRepositoryMock = (): Mocked> => { return { encodeImage: vitest.fn(), encodeText: vitest.fn(), diff --git a/server/test/utils.ts b/server/test/utils.ts index 9816266a210f6..fbff7ba00ddad 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -2,8 +2,6 @@ import { ChildProcessWithoutNullStreams } from 'node:child_process'; import { Writable } from 'node:stream'; import { PNG } from 'pngjs'; import { ImmichWorker } from 'src/enum'; -import { IEventRepository } from 'src/interfaces/event.interface'; -import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { AccessRepository } from 'src/repositories/access.repository'; import { ActivityRepository } from 'src/repositories/activity.repository'; import { AlbumUserRepository } from 'src/repositories/album-user.repository'; @@ -15,9 +13,11 @@ import { ConfigRepository } from 'src/repositories/config.repository'; import { CronRepository } from 'src/repositories/cron.repository'; import { CryptoRepository } from 'src/repositories/crypto.repository'; import { DatabaseRepository } from 'src/repositories/database.repository'; +import { EventRepository } from 'src/repositories/event.repository'; import { JobRepository } from 'src/repositories/job.repository'; import { LibraryRepository } from 'src/repositories/library.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; +import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; import { MapRepository } from 'src/repositories/map.repository'; import { MediaRepository } from 'src/repositories/media.repository'; import { MemoryRepository } from 'src/repositories/memory.repository'; @@ -108,11 +108,11 @@ export type ServiceMocks = { cron: Mocked>; crypto: Mocked>; database: Mocked>; - event: Mocked; + event: Mocked>; job: Mocked>; library: Mocked>; logger: Mocked; - machineLearning: Mocked; + machineLearning: Mocked>; map: Mocked>; media: Mocked>; memory: Mocked>; @@ -198,11 +198,11 @@ export const newTestService = ( cronMock as RepositoryInterface as CronRepository, cryptoMock as RepositoryInterface as CryptoRepository, databaseMock as RepositoryInterface as DatabaseRepository, - eventMock, + eventMock as RepositoryInterface as EventRepository, jobMock, apiKeyMock as RepositoryInterface as ApiKeyRepository, libraryMock as RepositoryInterface as LibraryRepository, - machineLearningMock, + machineLearningMock as RepositoryInterface as MachineLearningRepository, mapMock as RepositoryInterface as MapRepository, mediaMock as RepositoryInterface as MediaRepository, memoryMock as RepositoryInterface as MemoryRepository,