0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-02-18 01:24:26 -05:00

refactor: repositories (#16038)

This commit is contained in:
Jason Rasmussen 2025-02-11 15:12:31 -05:00 committed by GitHub
parent 9d85272c2b
commit 5f3a42a132
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 216 additions and 242 deletions

View file

@ -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');
}

View file

@ -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

View file

@ -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,
}

View file

@ -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<T extends EmitEvent> = (...args: ArgsOf<T>) => Promise<void> | void;
export type ArgOf<T extends EmitEvent> = EventMap[T][0];
export type ArgsOf<T extends EmitEvent> = 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<T extends EmitEvent> = {
event: T;
handler: EmitHandler<T>;
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<unknown>[] }): void;
emit<T extends keyof EventMap>(event: T, ...args: ArgsOf<T>): Promise<void>;
/**
* Send to connected clients for a specific user
*/
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;
/**
* Send to all connected servers
*/
serverSend<T extends ServerEvents>(event: T, ...args: ArgsOf<T>): void;
}

View file

@ -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<string>;
encodeText(urls: string[], text: string, config: ModelOptions): Promise<string>;
detectFaces(urls: string[], imagePath: string, config: FaceDetectionOptions): Promise<DetectedFaces>;
}

View file

@ -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<EventItem<T>> }>;
@ -37,14 +31,99 @@ type Item<T extends EmitEvent> = {
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<T extends EmitEvent> = (...args: ArgsOf<T>) => Promise<void> | void;
export type ArgOf<T extends EmitEvent> = EventMap[T][0];
export type ArgsOf<T extends EmitEvent> = 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<T extends EmitEvent> = {
event: T;
handler: EmitHandler<T>;
server: boolean;
};
export type AuthFn = (client: Socket) => Promise<AuthDto>;
@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<AuthDto>) {
this.authFn = fn;
}
private async authenticate(client: Socket) {
if (!this.authFn) {
throw new Error('Auth function not set');
}
return this.authFn(client);
}
}

View file

@ -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 }];

View file

@ -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);

View file

@ -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);
}

View file

@ -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';

View file

@ -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,

View file

@ -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';

View file

@ -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 => {

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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 => {

View file

@ -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,

View file

@ -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<IEventRepository> => {
export const newEventRepositoryMock = (): Mocked<RepositoryInterface<EventRepository>> => {
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(),
};
};

View file

@ -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<IMachineLearningRepository> => {
export const newMachineLearningRepositoryMock = (): Mocked<RepositoryInterface<MachineLearningRepository>> => {
return {
encodeImage: vitest.fn(),
encodeText: vitest.fn(),

View file

@ -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<RepositoryInterface<CronRepository>>;
crypto: Mocked<RepositoryInterface<CryptoRepository>>;
database: Mocked<RepositoryInterface<DatabaseRepository>>;
event: Mocked<IEventRepository>;
event: Mocked<RepositoryInterface<EventRepository>>;
job: Mocked<RepositoryInterface<JobRepository>>;
library: Mocked<RepositoryInterface<LibraryRepository>>;
logger: Mocked<ILoggingRepository>;
machineLearning: Mocked<IMachineLearningRepository>;
machineLearning: Mocked<RepositoryInterface<MachineLearningRepository>>;
map: Mocked<RepositoryInterface<MapRepository>>;
media: Mocked<RepositoryInterface<MediaRepository>>;
memory: Mocked<RepositoryInterface<MemoryRepository>>;
@ -198,11 +198,11 @@ export const newTestService = <T extends BaseService>(
cronMock as RepositoryInterface<CronRepository> as CronRepository,
cryptoMock as RepositoryInterface<CryptoRepository> as CryptoRepository,
databaseMock as RepositoryInterface<DatabaseRepository> as DatabaseRepository,
eventMock,
eventMock as RepositoryInterface<EventRepository> as EventRepository,
jobMock,
apiKeyMock as RepositoryInterface<ApiKeyRepository> as ApiKeyRepository,
libraryMock as RepositoryInterface<LibraryRepository> as LibraryRepository,
machineLearningMock,
machineLearningMock as RepositoryInterface<MachineLearningRepository> as MachineLearningRepository,
mapMock as RepositoryInterface<MapRepository> as MapRepository,
mediaMock as RepositoryInterface<MediaRepository> as MediaRepository,
memoryMock as RepositoryInterface<MemoryRepository> as MemoryRepository,