mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:39:37 -05:00 
			
		
		
		
	feat: use ILoggerRepository (#8855)
* Migrate ImmichLogger over to injected ILoggerRepository * chore: cleanup and tests --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
		
							parent
							
								
									877207a2e6
								
							
						
					
					
						commit
						6e6deec40c
					
				@ -9,7 +9,7 @@ import { UserService } from 'src/services/user.service';
 | 
				
			|||||||
export class ResetAdminPasswordCommand extends CommandRunner {
 | 
					export class ResetAdminPasswordCommand extends CommandRunner {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private userService: UserService,
 | 
					    private userService: UserService,
 | 
				
			||||||
    private readonly inquirer: InquirerService,
 | 
					    private inquirer: InquirerService,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    super();
 | 
					    super();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@ import { UUIDParamDto } from 'src/validation';
 | 
				
			|||||||
@Controller('shared-link')
 | 
					@Controller('shared-link')
 | 
				
			||||||
@Authenticated()
 | 
					@Authenticated()
 | 
				
			||||||
export class SharedLinkController {
 | 
					export class SharedLinkController {
 | 
				
			||||||
  constructor(private readonly service: SharedLinkService) {}
 | 
					  constructor(private service: SharedLinkService) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Get()
 | 
					  @Get()
 | 
				
			||||||
  getAllSharedLinks(@Auth() auth: AuthDto): Promise<SharedLinkResponseDto[]> {
 | 
					  getAllSharedLinks(@Auth() auth: AuthDto): Promise<SharedLinkResponseDto[]> {
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,7 @@ import { SystemConfigService } from 'src/services/system-config.service';
 | 
				
			|||||||
@Controller('system-config')
 | 
					@Controller('system-config')
 | 
				
			||||||
@Authenticated({ admin: true })
 | 
					@Authenticated({ admin: true })
 | 
				
			||||||
export class SystemConfigController {
 | 
					export class SystemConfigController {
 | 
				
			||||||
  constructor(private readonly service: SystemConfigService) {}
 | 
					  constructor(private service: SystemConfigService) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Get()
 | 
					  @Get()
 | 
				
			||||||
  getConfig(): Promise<SystemConfigDto> {
 | 
					  getConfig(): Promise<SystemConfigDto> {
 | 
				
			||||||
 | 
				
			|||||||
@ -7,11 +7,11 @@ import { PersonEntity } from 'src/entities/person.entity';
 | 
				
			|||||||
import { ImageFormat } from 'src/entities/system-config.entity';
 | 
					import { ImageFormat } from 'src/entities/system-config.entity';
 | 
				
			||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
					import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
				
			||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
					import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
					import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
				
			||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
					import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum StorageFolder {
 | 
					export enum StorageFolder {
 | 
				
			||||||
  ENCODED_VIDEO = 'encoded-video',
 | 
					  ENCODED_VIDEO = 'encoded-video',
 | 
				
			||||||
@ -41,35 +41,37 @@ export type GeneratedAssetType = GeneratedImageType | AssetPathType.ENCODED_VIDE
 | 
				
			|||||||
let instance: StorageCore | null;
 | 
					let instance: StorageCore | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class StorageCore {
 | 
					export class StorageCore {
 | 
				
			||||||
  private logger = new ImmichLogger(StorageCore.name);
 | 
					 | 
				
			||||||
  private configCore;
 | 
					  private configCore;
 | 
				
			||||||
  private constructor(
 | 
					  private constructor(
 | 
				
			||||||
    private assetRepository: IAssetRepository,
 | 
					    private assetRepository: IAssetRepository,
 | 
				
			||||||
 | 
					    private cryptoRepository: ICryptoRepository,
 | 
				
			||||||
    private moveRepository: IMoveRepository,
 | 
					    private moveRepository: IMoveRepository,
 | 
				
			||||||
    private personRepository: IPersonRepository,
 | 
					    private personRepository: IPersonRepository,
 | 
				
			||||||
    private cryptoRepository: ICryptoRepository,
 | 
					    private storageRepository: IStorageRepository,
 | 
				
			||||||
    private repository: IStorageRepository,
 | 
					 | 
				
			||||||
    systemConfigRepository: ISystemConfigRepository,
 | 
					    systemConfigRepository: ISystemConfigRepository,
 | 
				
			||||||
 | 
					    private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.configCore = SystemConfigCore.create(systemConfigRepository);
 | 
					    this.configCore = SystemConfigCore.create(systemConfigRepository, this.logger);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static create(
 | 
					  static create(
 | 
				
			||||||
    assetRepository: IAssetRepository,
 | 
					    assetRepository: IAssetRepository,
 | 
				
			||||||
 | 
					    cryptoRepository: ICryptoRepository,
 | 
				
			||||||
    moveRepository: IMoveRepository,
 | 
					    moveRepository: IMoveRepository,
 | 
				
			||||||
    personRepository: IPersonRepository,
 | 
					    personRepository: IPersonRepository,
 | 
				
			||||||
    cryptoRepository: ICryptoRepository,
 | 
					    storageRepository: IStorageRepository,
 | 
				
			||||||
    configRepository: ISystemConfigRepository,
 | 
					    systemConfigRepository: ISystemConfigRepository,
 | 
				
			||||||
    repository: IStorageRepository,
 | 
					    logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    if (!instance) {
 | 
					    if (!instance) {
 | 
				
			||||||
      instance = new StorageCore(
 | 
					      instance = new StorageCore(
 | 
				
			||||||
        assetRepository,
 | 
					        assetRepository,
 | 
				
			||||||
 | 
					        cryptoRepository,
 | 
				
			||||||
        moveRepository,
 | 
					        moveRepository,
 | 
				
			||||||
        personRepository,
 | 
					        personRepository,
 | 
				
			||||||
        cryptoRepository,
 | 
					        storageRepository,
 | 
				
			||||||
        repository,
 | 
					        systemConfigRepository,
 | 
				
			||||||
        configRepository,
 | 
					        logger,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -170,8 +172,8 @@ export class StorageCore {
 | 
				
			|||||||
    let move = await this.moveRepository.getByEntity(entityId, pathType);
 | 
					    let move = await this.moveRepository.getByEntity(entityId, pathType);
 | 
				
			||||||
    if (move) {
 | 
					    if (move) {
 | 
				
			||||||
      this.logger.log(`Attempting to finish incomplete move: ${move.oldPath} => ${move.newPath}`);
 | 
					      this.logger.log(`Attempting to finish incomplete move: ${move.oldPath} => ${move.newPath}`);
 | 
				
			||||||
      const oldPathExists = await this.repository.checkFileExists(move.oldPath);
 | 
					      const oldPathExists = await this.storageRepository.checkFileExists(move.oldPath);
 | 
				
			||||||
      const newPathExists = await this.repository.checkFileExists(move.newPath);
 | 
					      const newPathExists = await this.storageRepository.checkFileExists(move.newPath);
 | 
				
			||||||
      const newPathCheck = newPathExists ? move.newPath : null;
 | 
					      const newPathCheck = newPathExists ? move.newPath : null;
 | 
				
			||||||
      const actualPath = oldPathExists ? move.oldPath : newPathCheck;
 | 
					      const actualPath = oldPathExists ? move.oldPath : newPathCheck;
 | 
				
			||||||
      if (!actualPath) {
 | 
					      if (!actualPath) {
 | 
				
			||||||
@ -205,7 +207,7 @@ export class StorageCore {
 | 
				
			|||||||
    if (move.oldPath !== newPath) {
 | 
					    if (move.oldPath !== newPath) {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        this.logger.debug(`Attempting to rename file: ${move.oldPath} => ${newPath}`);
 | 
					        this.logger.debug(`Attempting to rename file: ${move.oldPath} => ${newPath}`);
 | 
				
			||||||
        await this.repository.rename(move.oldPath, newPath);
 | 
					        await this.storageRepository.rename(move.oldPath, newPath);
 | 
				
			||||||
      } catch (error: any) {
 | 
					      } catch (error: any) {
 | 
				
			||||||
        if (error.code !== 'EXDEV') {
 | 
					        if (error.code !== 'EXDEV') {
 | 
				
			||||||
          this.logger.warn(
 | 
					          this.logger.warn(
 | 
				
			||||||
@ -214,19 +216,19 @@ export class StorageCore {
 | 
				
			|||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.logger.debug(`Unable to rename file. Falling back to copy, verify and delete`);
 | 
					        this.logger.debug(`Unable to rename file. Falling back to copy, verify and delete`);
 | 
				
			||||||
        await this.repository.copyFile(move.oldPath, newPath);
 | 
					        await this.storageRepository.copyFile(move.oldPath, newPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!(await this.verifyNewPathContentsMatchesExpected(move.oldPath, newPath, assetInfo))) {
 | 
					        if (!(await this.verifyNewPathContentsMatchesExpected(move.oldPath, newPath, assetInfo))) {
 | 
				
			||||||
          this.logger.warn(`Skipping move due to file size mismatch`);
 | 
					          this.logger.warn(`Skipping move due to file size mismatch`);
 | 
				
			||||||
          await this.repository.unlink(newPath);
 | 
					          await this.storageRepository.unlink(newPath);
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const { atime, mtime } = await this.repository.stat(move.oldPath);
 | 
					        const { atime, mtime } = await this.storageRepository.stat(move.oldPath);
 | 
				
			||||||
        await this.repository.utimes(newPath, atime, mtime);
 | 
					        await this.storageRepository.utimes(newPath, atime, mtime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          await this.repository.unlink(move.oldPath);
 | 
					          await this.storageRepository.unlink(move.oldPath);
 | 
				
			||||||
        } catch (error: any) {
 | 
					        } catch (error: any) {
 | 
				
			||||||
          this.logger.warn(`Unable to delete old file, it will now no longer be tracked by Immich: ${error.message}`);
 | 
					          this.logger.warn(`Unable to delete old file, it will now no longer be tracked by Immich: ${error.message}`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -242,8 +244,8 @@ export class StorageCore {
 | 
				
			|||||||
    newPath: string,
 | 
					    newPath: string,
 | 
				
			||||||
    assetInfo?: { sizeInBytes: number; checksum: Buffer },
 | 
					    assetInfo?: { sizeInBytes: number; checksum: Buffer },
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    const oldStat = await this.repository.stat(oldPath);
 | 
					    const oldStat = await this.storageRepository.stat(oldPath);
 | 
				
			||||||
    const newStat = await this.repository.stat(newPath);
 | 
					    const newStat = await this.storageRepository.stat(newPath);
 | 
				
			||||||
    const oldPathSize = assetInfo ? assetInfo.sizeInBytes : oldStat.size;
 | 
					    const oldPathSize = assetInfo ? assetInfo.sizeInBytes : oldStat.size;
 | 
				
			||||||
    const newPathSize = newStat.size;
 | 
					    const newPathSize = newStat.size;
 | 
				
			||||||
    this.logger.debug(`File size check: ${newPathSize} === ${oldPathSize}`);
 | 
					    this.logger.debug(`File size check: ${newPathSize} === ${oldPathSize}`);
 | 
				
			||||||
@ -269,11 +271,11 @@ export class StorageCore {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ensureFolders(input: string) {
 | 
					  ensureFolders(input: string) {
 | 
				
			||||||
    this.repository.mkdirSync(dirname(input));
 | 
					    this.storageRepository.mkdirSync(dirname(input));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  removeEmptyDirs(folder: StorageFolder) {
 | 
					  removeEmptyDirs(folder: StorageFolder) {
 | 
				
			||||||
    return this.repository.removeEmptyDirs(StorageCore.getBaseFolder(folder));
 | 
					    return this.storageRepository.removeEmptyDirs(StorageCore.getBaseFolder(folder));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private savePath(pathType: PathType, id: string, newPath: string) {
 | 
					  private savePath(pathType: PathType, id: string, newPath: string) {
 | 
				
			||||||
 | 
				
			|||||||
@ -22,8 +22,8 @@ import {
 | 
				
			|||||||
  VideoCodec,
 | 
					  VideoCodec,
 | 
				
			||||||
} from 'src/entities/system-config.entity';
 | 
					} from 'src/entities/system-config.entity';
 | 
				
			||||||
import { QueueName } from 'src/interfaces/job.interface';
 | 
					import { QueueName } from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise<void>;
 | 
					export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise<void>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -169,16 +169,18 @@ let instance: SystemConfigCore | null;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class SystemConfigCore {
 | 
					export class SystemConfigCore {
 | 
				
			||||||
  private logger = new ImmichLogger(SystemConfigCore.name);
 | 
					 | 
				
			||||||
  private configCache: SystemConfigEntity<SystemConfigValue>[] | null = null;
 | 
					  private configCache: SystemConfigEntity<SystemConfigValue>[] | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public config$ = new Subject<SystemConfig>();
 | 
					  public config$ = new Subject<SystemConfig>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private constructor(private repository: ISystemConfigRepository) {}
 | 
					  private constructor(
 | 
				
			||||||
 | 
					    private repository: ISystemConfigRepository,
 | 
				
			||||||
 | 
					    private logger: ILoggerRepository,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static create(repository: ISystemConfigRepository) {
 | 
					  static create(repository: ISystemConfigRepository, logger: ILoggerRepository) {
 | 
				
			||||||
    if (!instance) {
 | 
					    if (!instance) {
 | 
				
			||||||
      instance = new SystemConfigCore(repository);
 | 
					      instance = new SystemConfigCore(repository, logger);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return instance;
 | 
					    return instance;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,7 @@ async function bootstrapMicroservices() {
 | 
				
			|||||||
  const host = String(process.env.HOST || '0.0.0.0');
 | 
					  const host = String(process.env.HOST || '0.0.0.0');
 | 
				
			||||||
  const port = Number(process.env.MICROSERVICES_PORT) || 3002;
 | 
					  const port = Number(process.env.MICROSERVICES_PORT) || 3002;
 | 
				
			||||||
  const app = await NestFactory.create(MicroservicesModule, { bufferLogs: true });
 | 
					  const app = await NestFactory.create(MicroservicesModule, { bufferLogs: true });
 | 
				
			||||||
  const logger = app.get(ILoggerRepository);
 | 
					  const logger = await app.resolve(ILoggerRepository);
 | 
				
			||||||
  logger.setContext('ImmichMicroservice');
 | 
					  logger.setContext('ImmichMicroservice');
 | 
				
			||||||
  app.useLogger(logger);
 | 
					  app.useLogger(logger);
 | 
				
			||||||
  app.useWebSocketAdapter(new WebSocketAdapter(app));
 | 
					  app.useWebSocketAdapter(new WebSocketAdapter(app));
 | 
				
			||||||
@ -36,7 +36,7 @@ async function bootstrapApi() {
 | 
				
			|||||||
  const host = String(process.env.HOST || '0.0.0.0');
 | 
					  const host = String(process.env.HOST || '0.0.0.0');
 | 
				
			||||||
  const port = Number(process.env.SERVER_PORT) || 3001;
 | 
					  const port = Number(process.env.SERVER_PORT) || 3001;
 | 
				
			||||||
  const app = await NestFactory.create<NestExpressApplication>(ApiModule, { bufferLogs: true });
 | 
					  const app = await NestFactory.create<NestExpressApplication>(ApiModule, { bufferLogs: true });
 | 
				
			||||||
  const logger = app.get(ILoggerRepository);
 | 
					  const logger = await app.resolve(ILoggerRepository);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  logger.setContext('ImmichServer');
 | 
					  logger.setContext('ImmichServer');
 | 
				
			||||||
  app.useLogger(logger);
 | 
					  app.useLogger(logger);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
 | 
					import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from '@nestjs/common';
 | 
				
			||||||
import { PATH_METADATA } from '@nestjs/common/constants';
 | 
					import { PATH_METADATA } from '@nestjs/common/constants';
 | 
				
			||||||
import { Reflector } from '@nestjs/core';
 | 
					import { Reflector } from '@nestjs/core';
 | 
				
			||||||
import { transformException } from '@nestjs/platform-express/multer/multer/multer.utils';
 | 
					import { transformException } from '@nestjs/platform-express/multer/multer/multer.utils';
 | 
				
			||||||
@ -7,9 +7,9 @@ import multer, { StorageEngine, diskStorage } from 'multer';
 | 
				
			|||||||
import { createHash, randomUUID } from 'node:crypto';
 | 
					import { createHash, randomUUID } from 'node:crypto';
 | 
				
			||||||
import { Observable } from 'rxjs';
 | 
					import { Observable } from 'rxjs';
 | 
				
			||||||
import { UploadFieldName } from 'src/dtos/asset.dto';
 | 
					import { UploadFieldName } from 'src/dtos/asset.dto';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { AuthRequest } from 'src/middleware/auth.guard';
 | 
					import { AuthRequest } from 'src/middleware/auth.guard';
 | 
				
			||||||
import { AssetService, UploadFile } from 'src/services/asset.service';
 | 
					import { AssetService, UploadFile } from 'src/services/asset.service';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum Route {
 | 
					export enum Route {
 | 
				
			||||||
  ASSET = 'asset',
 | 
					  ASSET = 'asset',
 | 
				
			||||||
@ -59,8 +59,6 @@ const asRequest = (request: AuthRequest, file: Express.Multer.File) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class FileUploadInterceptor implements NestInterceptor {
 | 
					export class FileUploadInterceptor implements NestInterceptor {
 | 
				
			||||||
  private logger = new ImmichLogger(FileUploadInterceptor.name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private handlers: {
 | 
					  private handlers: {
 | 
				
			||||||
    userProfile: RequestHandler;
 | 
					    userProfile: RequestHandler;
 | 
				
			||||||
    assetUpload: RequestHandler;
 | 
					    assetUpload: RequestHandler;
 | 
				
			||||||
@ -70,7 +68,10 @@ export class FileUploadInterceptor implements NestInterceptor {
 | 
				
			|||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private reflect: Reflector,
 | 
					    private reflect: Reflector,
 | 
				
			||||||
    private assetService: AssetService,
 | 
					    private assetService: AssetService,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
 | 
					    this.logger.setContext(FileUploadInterceptor.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.defaultStorage = diskStorage({
 | 
					    this.defaultStorage = diskStorage({
 | 
				
			||||||
      filename: this.filename.bind(this),
 | 
					      filename: this.filename.bind(this),
 | 
				
			||||||
      destination: this.destination.bind(this),
 | 
					      destination: this.destination.bind(this),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
import { InjectRepository } from '@nestjs/typeorm';
 | 
					import { InjectRepository } from '@nestjs/typeorm';
 | 
				
			||||||
import { ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
 | 
					import { ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
 | 
				
			||||||
import { ActivityEntity } from 'src/entities/activity.entity';
 | 
					import { ActivityEntity } from 'src/entities/activity.entity';
 | 
				
			||||||
@ -25,6 +26,7 @@ type IPersonAccess = IAccessRepository['person'];
 | 
				
			|||||||
type IPartnerAccess = IAccessRepository['partner'];
 | 
					type IPartnerAccess = IAccessRepository['partner'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Instrumentation()
 | 
					@Instrumentation()
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
class ActivityAccess implements IActivityAccess {
 | 
					class ActivityAccess implements IActivityAccess {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private activityRepository: Repository<ActivityEntity>,
 | 
					    private activityRepository: Repository<ActivityEntity>,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
import { InjectRepository } from '@nestjs/typeorm';
 | 
					import { InjectRepository } from '@nestjs/typeorm';
 | 
				
			||||||
import { AuditEntity } from 'src/entities/audit.entity';
 | 
					import { AuditEntity } from 'src/entities/audit.entity';
 | 
				
			||||||
import { AuditSearch, IAuditRepository } from 'src/interfaces/audit.interface';
 | 
					import { AuditSearch, IAuditRepository } from 'src/interfaces/audit.interface';
 | 
				
			||||||
@ -5,6 +6,7 @@ import { Instrumentation } from 'src/utils/instrumentation';
 | 
				
			|||||||
import { In, LessThan, MoreThan, Repository } from 'typeorm';
 | 
					import { In, LessThan, MoreThan, Repository } from 'typeorm';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Instrumentation()
 | 
					@Instrumentation()
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
export class AuditRepository implements IAuditRepository {
 | 
					export class AuditRepository implements IAuditRepository {
 | 
				
			||||||
  constructor(@InjectRepository(AuditEntity) private repository: Repository<AuditEntity>) {}
 | 
					  constructor(@InjectRepository(AuditEntity) private repository: Repository<AuditEntity>) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { Injectable } from '@nestjs/common';
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
import { InjectDataSource } from '@nestjs/typeorm';
 | 
					import { InjectDataSource } from '@nestjs/typeorm';
 | 
				
			||||||
import AsyncLock from 'async-lock';
 | 
					import AsyncLock from 'async-lock';
 | 
				
			||||||
import { vectorExt } from 'src/database.config';
 | 
					import { vectorExt } from 'src/database.config';
 | 
				
			||||||
@ -11,8 +11,8 @@ import {
 | 
				
			|||||||
  VectorUpdateResult,
 | 
					  VectorUpdateResult,
 | 
				
			||||||
  extName,
 | 
					  extName,
 | 
				
			||||||
} from 'src/interfaces/database.interface';
 | 
					} from 'src/interfaces/database.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { Instrumentation } from 'src/utils/instrumentation';
 | 
					import { Instrumentation } from 'src/utils/instrumentation';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { Version, VersionType } from 'src/utils/version';
 | 
					import { Version, VersionType } from 'src/utils/version';
 | 
				
			||||||
import { isValidInteger } from 'src/validation';
 | 
					import { isValidInteger } from 'src/validation';
 | 
				
			||||||
import { DataSource, EntityManager, QueryRunner } from 'typeorm';
 | 
					import { DataSource, EntityManager, QueryRunner } from 'typeorm';
 | 
				
			||||||
@ -20,10 +20,14 @@ import { DataSource, EntityManager, QueryRunner } from 'typeorm';
 | 
				
			|||||||
@Instrumentation()
 | 
					@Instrumentation()
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class DatabaseRepository implements IDatabaseRepository {
 | 
					export class DatabaseRepository implements IDatabaseRepository {
 | 
				
			||||||
  private logger = new ImmichLogger(DatabaseRepository.name);
 | 
					 | 
				
			||||||
  readonly asyncLock = new AsyncLock();
 | 
					  readonly asyncLock = new AsyncLock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(@InjectDataSource() private dataSource: DataSource) {}
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @InjectDataSource() private dataSource: DataSource,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    this.logger.setContext(DatabaseRepository.name);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async getExtensionVersion(extension: DatabaseExtension): Promise<Version | null> {
 | 
					  async getExtensionVersion(extension: DatabaseExtension): Promise<Version | null> {
 | 
				
			||||||
    const res = await this.dataSource.query(`SELECT extversion FROM pg_extension WHERE extname = $1`, [extension]);
 | 
					    const res = await this.dataSource.query(`SELECT extversion FROM pg_extension WHERE extname = $1`, [extension]);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
 | 
					import { EventEmitter2 } from '@nestjs/event-emitter';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  OnGatewayConnection,
 | 
					  OnGatewayConnection,
 | 
				
			||||||
@ -14,9 +15,9 @@ import {
 | 
				
			|||||||
  ServerEvent,
 | 
					  ServerEvent,
 | 
				
			||||||
  ServerEventMap,
 | 
					  ServerEventMap,
 | 
				
			||||||
} from 'src/interfaces/event.interface';
 | 
					} from 'src/interfaces/event.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { AuthService } from 'src/services/auth.service';
 | 
					import { AuthService } from 'src/services/auth.service';
 | 
				
			||||||
import { Instrumentation } from 'src/utils/instrumentation';
 | 
					import { Instrumentation } from 'src/utils/instrumentation';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Instrumentation()
 | 
					@Instrumentation()
 | 
				
			||||||
@WebSocketGateway({
 | 
					@WebSocketGateway({
 | 
				
			||||||
@ -24,16 +25,18 @@ import { ImmichLogger } from 'src/utils/logger';
 | 
				
			|||||||
  path: '/api/socket.io',
 | 
					  path: '/api/socket.io',
 | 
				
			||||||
  transports: ['websocket'],
 | 
					  transports: ['websocket'],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, IEventRepository {
 | 
					export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, IEventRepository {
 | 
				
			||||||
  private logger = new ImmichLogger(EventRepository.name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @WebSocketServer()
 | 
					  @WebSocketServer()
 | 
				
			||||||
  private server?: Server;
 | 
					  private server?: Server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private authService: AuthService,
 | 
					    private authService: AuthService,
 | 
				
			||||||
    private eventEmitter: EventEmitter2,
 | 
					    private eventEmitter: EventEmitter2,
 | 
				
			||||||
  ) {}
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    this.logger.setContext(EventRepository.name);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterInit(server: Server) {
 | 
					  afterInit(server: Server) {
 | 
				
			||||||
    this.logger.log('Initialized websocket server');
 | 
					    this.logger.log('Initialized websocket server');
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { getQueueToken } from '@nestjs/bullmq';
 | 
					import { getQueueToken } from '@nestjs/bullmq';
 | 
				
			||||||
import { Injectable } from '@nestjs/common';
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
import { ModuleRef } from '@nestjs/core';
 | 
					import { ModuleRef } from '@nestjs/core';
 | 
				
			||||||
import { SchedulerRegistry } from '@nestjs/schedule';
 | 
					import { SchedulerRegistry } from '@nestjs/schedule';
 | 
				
			||||||
import { Job, JobsOptions, Processor, Queue, Worker, WorkerOptions } from 'bullmq';
 | 
					import { Job, JobsOptions, Processor, Queue, Worker, WorkerOptions } from 'bullmq';
 | 
				
			||||||
@ -15,8 +15,8 @@ import {
 | 
				
			|||||||
  QueueName,
 | 
					  QueueName,
 | 
				
			||||||
  QueueStatus,
 | 
					  QueueStatus,
 | 
				
			||||||
} from 'src/interfaces/job.interface';
 | 
					} from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { Instrumentation } from 'src/utils/instrumentation';
 | 
					import { Instrumentation } from 'src/utils/instrumentation';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
 | 
					export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
 | 
				
			||||||
  // misc
 | 
					  // misc
 | 
				
			||||||
@ -83,12 +83,14 @@ export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
 | 
				
			|||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class JobRepository implements IJobRepository {
 | 
					export class JobRepository implements IJobRepository {
 | 
				
			||||||
  private workers: Partial<Record<QueueName, Worker>> = {};
 | 
					  private workers: Partial<Record<QueueName, Worker>> = {};
 | 
				
			||||||
  private logger = new ImmichLogger(JobRepository.name);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private moduleReference: ModuleRef,
 | 
					    private moduleReference: ModuleRef,
 | 
				
			||||||
    private schedulerReqistry: SchedulerRegistry,
 | 
					    private schedulerReqistry: SchedulerRegistry,
 | 
				
			||||||
  ) {}
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    this.logger.setContext(JobRepository.name);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  addHandler(queueName: QueueName, concurrency: number, handler: (item: JobItem) => Promise<void>) {
 | 
					  addHandler(queueName: QueueName, concurrency: number, handler: (item: JobItem) => Promise<void>) {
 | 
				
			||||||
    const workerHandler: Processor = async (job: Job) => handler(job as JobItem);
 | 
					    const workerHandler: Processor = async (job: Job) => handler(job as JobItem);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,10 @@
 | 
				
			|||||||
import { Injectable } from '@nestjs/common';
 | 
					import { Injectable, Scope } from '@nestjs/common';
 | 
				
			||||||
import { ClsService } from 'nestjs-cls';
 | 
					import { ClsService } from 'nestjs-cls';
 | 
				
			||||||
import { LogLevel } from 'src/entities/system-config.entity';
 | 
					import { LogLevel } from 'src/entities/system-config.entity';
 | 
				
			||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					import { ImmichLogger } from 'src/utils/logger';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable({ scope: Scope.TRANSIENT })
 | 
				
			||||||
export class LoggerRepository extends ImmichLogger implements ILoggerRepository {
 | 
					export class LoggerRepository extends ImmichLogger implements ILoggerRepository {
 | 
				
			||||||
  constructor(private cls: ClsService) {
 | 
					  constructor(private cls: ClsService) {
 | 
				
			||||||
    super(LoggerRepository.name);
 | 
					    super(LoggerRepository.name);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,11 @@
 | 
				
			|||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
import ffmpeg, { FfprobeData } from 'fluent-ffmpeg';
 | 
					import ffmpeg, { FfprobeData } from 'fluent-ffmpeg';
 | 
				
			||||||
import fs from 'node:fs/promises';
 | 
					import fs from 'node:fs/promises';
 | 
				
			||||||
import { Writable } from 'node:stream';
 | 
					import { Writable } from 'node:stream';
 | 
				
			||||||
import { promisify } from 'node:util';
 | 
					import { promisify } from 'node:util';
 | 
				
			||||||
import sharp from 'sharp';
 | 
					import sharp from 'sharp';
 | 
				
			||||||
import { Colorspace } from 'src/entities/system-config.entity';
 | 
					import { Colorspace } from 'src/entities/system-config.entity';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  CropOptions,
 | 
					  CropOptions,
 | 
				
			||||||
  IMediaRepository,
 | 
					  IMediaRepository,
 | 
				
			||||||
@ -12,7 +14,6 @@ import {
 | 
				
			|||||||
  VideoInfo,
 | 
					  VideoInfo,
 | 
				
			||||||
} from 'src/interfaces/media.interface';
 | 
					} from 'src/interfaces/media.interface';
 | 
				
			||||||
import { Instrumentation } from 'src/utils/instrumentation';
 | 
					import { Instrumentation } from 'src/utils/instrumentation';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { handlePromiseError } from 'src/utils/misc';
 | 
					import { handlePromiseError } from 'src/utils/misc';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const probe = promisify<string, FfprobeData>(ffmpeg.ffprobe);
 | 
					const probe = promisify<string, FfprobeData>(ffmpeg.ffprobe);
 | 
				
			||||||
@ -20,9 +21,11 @@ sharp.concurrency(0);
 | 
				
			|||||||
sharp.cache({ files: 0 });
 | 
					sharp.cache({ files: 0 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Instrumentation()
 | 
					@Instrumentation()
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
export class MediaRepository implements IMediaRepository {
 | 
					export class MediaRepository implements IMediaRepository {
 | 
				
			||||||
  private logger = new ImmichLogger(MediaRepository.name);
 | 
					  constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
 | 
				
			||||||
 | 
					    this.logger.setContext(MediaRepository.name);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  crop(input: string | Buffer, options: CropOptions): Promise<Buffer> {
 | 
					  crop(input: string | Buffer, options: CropOptions): Promise<Buffer> {
 | 
				
			||||||
    return sharp(input, { failOn: 'none' })
 | 
					    return sharp(input, { failOn: 'none' })
 | 
				
			||||||
      .pipelineColorspace('rgb16')
 | 
					      .pipelineColorspace('rgb16')
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { Inject } from '@nestjs/common';
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
 | 
					import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
 | 
				
			||||||
import { DefaultReadTaskOptions, Tags, exiftool } from 'exiftool-vendored';
 | 
					import { DefaultReadTaskOptions, Tags, exiftool } from 'exiftool-vendored';
 | 
				
			||||||
import geotz from 'geo-tz';
 | 
					import geotz from 'geo-tz';
 | 
				
			||||||
@ -11,24 +11,26 @@ import { DummyValue, GenerateSql } from 'src/decorators';
 | 
				
			|||||||
import { ExifEntity } from 'src/entities/exif.entity';
 | 
					import { ExifEntity } from 'src/entities/exif.entity';
 | 
				
			||||||
import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
 | 
					import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
 | 
				
			||||||
import { SystemMetadataKey } from 'src/entities/system-metadata.entity';
 | 
					import { SystemMetadataKey } from 'src/entities/system-metadata.entity';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { GeoPoint, IMetadataRepository, ImmichTags, ReverseGeocodeResult } from 'src/interfaces/metadata.interface';
 | 
					import { GeoPoint, IMetadataRepository, ImmichTags, ReverseGeocodeResult } from 'src/interfaces/metadata.interface';
 | 
				
			||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
 | 
					import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
 | 
				
			||||||
import { Instrumentation } from 'src/utils/instrumentation';
 | 
					import { Instrumentation } from 'src/utils/instrumentation';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { DataSource, QueryRunner, Repository } from 'typeorm';
 | 
					import { DataSource, QueryRunner, Repository } from 'typeorm';
 | 
				
			||||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
 | 
					import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Instrumentation()
 | 
					@Instrumentation()
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
export class MetadataRepository implements IMetadataRepository {
 | 
					export class MetadataRepository implements IMetadataRepository {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @InjectRepository(ExifEntity) private exifRepository: Repository<ExifEntity>,
 | 
					    @InjectRepository(ExifEntity) private exifRepository: Repository<ExifEntity>,
 | 
				
			||||||
    @InjectRepository(GeodataPlacesEntity) private readonly geodataPlacesRepository: Repository<GeodataPlacesEntity>,
 | 
					    @InjectRepository(GeodataPlacesEntity) private geodataPlacesRepository: Repository<GeodataPlacesEntity>,
 | 
				
			||||||
    @Inject(ISystemMetadataRepository)
 | 
					    @Inject(ISystemMetadataRepository)
 | 
				
			||||||
    private readonly systemMetadataRepository: ISystemMetadataRepository,
 | 
					    private systemMetadataRepository: ISystemMetadataRepository,
 | 
				
			||||||
    @InjectDataSource() private dataSource: DataSource,
 | 
					    @InjectDataSource() private dataSource: DataSource,
 | 
				
			||||||
  ) {}
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
  private logger = new ImmichLogger(MetadataRepository.name);
 | 
					    this.logger.setContext(MetadataRepository.name);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async init(): Promise<void> {
 | 
					  async init(): Promise<void> {
 | 
				
			||||||
    this.logger.log('Initializing metadata repository');
 | 
					    this.logger.log('Initializing metadata repository');
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ import { apiMetrics, hostMetrics, jobMetrics, repoMetrics } from 'src/utils/inst
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class MetricGroupRepository implements IMetricGroupRepository {
 | 
					class MetricGroupRepository implements IMetricGroupRepository {
 | 
				
			||||||
  private enabled = false;
 | 
					  private enabled = false;
 | 
				
			||||||
  constructor(private readonly metricService: MetricService) {}
 | 
					  constructor(private metricService: MetricService) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  addToCounter(name: string, value: number, options?: MetricOptions): void {
 | 
					  addToCounter(name: string, value: number, options?: MetricOptions): void {
 | 
				
			||||||
    if (this.enabled) {
 | 
					    if (this.enabled) {
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,7 @@ import { DeepPartial, Repository } from 'typeorm';
 | 
				
			|||||||
@Instrumentation()
 | 
					@Instrumentation()
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class PartnerRepository implements IPartnerRepository {
 | 
					export class PartnerRepository implements IPartnerRepository {
 | 
				
			||||||
  constructor(@InjectRepository(PartnerEntity) private readonly repository: Repository<PartnerEntity>) {}
 | 
					  constructor(@InjectRepository(PartnerEntity) private repository: Repository<PartnerEntity>) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getAll(userId: string): Promise<PartnerEntity[]> {
 | 
					  getAll(userId: string): Promise<PartnerEntity[]> {
 | 
				
			||||||
    return this.repository.find({ where: [{ sharedWithId: userId }, { sharedById: userId }] });
 | 
					    return this.repository.find({ where: [{ sharedWithId: userId }, { sharedById: userId }] });
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
import { InjectRepository } from '@nestjs/typeorm';
 | 
					import { InjectRepository } from '@nestjs/typeorm';
 | 
				
			||||||
import _ from 'lodash';
 | 
					import _ from 'lodash';
 | 
				
			||||||
import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
 | 
					import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
 | 
				
			||||||
@ -19,6 +20,7 @@ import { Paginated, PaginationOptions, paginate } from 'src/utils/pagination';
 | 
				
			|||||||
import { FindManyOptions, FindOptionsRelations, FindOptionsSelect, In, Repository } from 'typeorm';
 | 
					import { FindManyOptions, FindOptionsRelations, FindOptionsSelect, In, Repository } from 'typeorm';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Instrumentation()
 | 
					@Instrumentation()
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
export class PersonRepository implements IPersonRepository {
 | 
					export class PersonRepository implements IPersonRepository {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
 | 
					    @InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { Injectable } from '@nestjs/common';
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
import { InjectRepository } from '@nestjs/typeorm';
 | 
					import { InjectRepository } from '@nestjs/typeorm';
 | 
				
			||||||
import { vectorExt } from 'src/database.config';
 | 
					import { vectorExt } from 'src/database.config';
 | 
				
			||||||
import { DummyValue, GenerateSql } from 'src/decorators';
 | 
					import { DummyValue, GenerateSql } from 'src/decorators';
 | 
				
			||||||
@ -8,6 +8,7 @@ import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
 | 
				
			|||||||
import { SmartInfoEntity } from 'src/entities/smart-info.entity';
 | 
					import { SmartInfoEntity } from 'src/entities/smart-info.entity';
 | 
				
			||||||
import { SmartSearchEntity } from 'src/entities/smart-search.entity';
 | 
					import { SmartSearchEntity } from 'src/entities/smart-search.entity';
 | 
				
			||||||
import { DatabaseExtension } from 'src/interfaces/database.interface';
 | 
					import { DatabaseExtension } from 'src/interfaces/database.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  AssetSearchOptions,
 | 
					  AssetSearchOptions,
 | 
				
			||||||
  FaceEmbeddingSearch,
 | 
					  FaceEmbeddingSearch,
 | 
				
			||||||
@ -18,7 +19,6 @@ import {
 | 
				
			|||||||
} from 'src/interfaces/search.interface';
 | 
					} from 'src/interfaces/search.interface';
 | 
				
			||||||
import { asVector, searchAssetBuilder } from 'src/utils/database';
 | 
					import { asVector, searchAssetBuilder } from 'src/utils/database';
 | 
				
			||||||
import { Instrumentation } from 'src/utils/instrumentation';
 | 
					import { Instrumentation } from 'src/utils/instrumentation';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { getCLIPModelInfo } from 'src/utils/misc';
 | 
					import { getCLIPModelInfo } from 'src/utils/misc';
 | 
				
			||||||
import { Paginated, PaginationMode, PaginationResult, paginatedBuilder } from 'src/utils/pagination';
 | 
					import { Paginated, PaginationMode, PaginationResult, paginatedBuilder } from 'src/utils/pagination';
 | 
				
			||||||
import { isValidInteger } from 'src/validation';
 | 
					import { isValidInteger } from 'src/validation';
 | 
				
			||||||
@ -27,7 +27,6 @@ import { Repository, SelectQueryBuilder } from 'typeorm';
 | 
				
			|||||||
@Instrumentation()
 | 
					@Instrumentation()
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class SearchRepository implements ISearchRepository {
 | 
					export class SearchRepository implements ISearchRepository {
 | 
				
			||||||
  private logger = new ImmichLogger(SearchRepository.name);
 | 
					 | 
				
			||||||
  private faceColumns: string[];
 | 
					  private faceColumns: string[];
 | 
				
			||||||
  private assetsByCityQuery: string;
 | 
					  private assetsByCityQuery: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -36,8 +35,10 @@ export class SearchRepository implements ISearchRepository {
 | 
				
			|||||||
    @InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
 | 
					    @InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
 | 
				
			||||||
    @InjectRepository(AssetFaceEntity) private assetFaceRepository: Repository<AssetFaceEntity>,
 | 
					    @InjectRepository(AssetFaceEntity) private assetFaceRepository: Repository<AssetFaceEntity>,
 | 
				
			||||||
    @InjectRepository(SmartSearchEntity) private smartSearchRepository: Repository<SmartSearchEntity>,
 | 
					    @InjectRepository(SmartSearchEntity) private smartSearchRepository: Repository<SmartSearchEntity>,
 | 
				
			||||||
    @InjectRepository(GeodataPlacesEntity) private readonly geodataPlacesRepository: Repository<GeodataPlacesEntity>,
 | 
					    @InjectRepository(GeodataPlacesEntity) private geodataPlacesRepository: Repository<GeodataPlacesEntity>,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
 | 
					    this.logger.setContext(SearchRepository.name);
 | 
				
			||||||
    this.faceColumns = this.assetFaceRepository.manager.connection
 | 
					    this.faceColumns = this.assetFaceRepository.manager.connection
 | 
				
			||||||
      .getMetadata(AssetFaceEntity)
 | 
					      .getMetadata(AssetFaceEntity)
 | 
				
			||||||
      .ownColumns.map((column) => column.propertyName)
 | 
					      .ownColumns.map((column) => column.propertyName)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
import mockfs from 'mock-fs';
 | 
					import mockfs from 'mock-fs';
 | 
				
			||||||
import { CrawlOptionsDto } from 'src/dtos/library.dto';
 | 
					import { CrawlOptionsDto } from 'src/dtos/library.dto';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { StorageRepository } from 'src/repositories/storage.repository';
 | 
					import { StorageRepository } from 'src/repositories/storage.repository';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Test {
 | 
					interface Test {
 | 
				
			||||||
  test: string;
 | 
					  test: string;
 | 
				
			||||||
@ -181,9 +183,11 @@ const tests: Test[] = [
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
describe(StorageRepository.name, () => {
 | 
					describe(StorageRepository.name, () => {
 | 
				
			||||||
  let sut: StorageRepository;
 | 
					  let sut: StorageRepository;
 | 
				
			||||||
 | 
					  let logger: ILoggerRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    sut = new StorageRepository();
 | 
					    logger = newLoggerRepositoryMock();
 | 
				
			||||||
 | 
					    sut = new StorageRepository(logger);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterEach(() => {
 | 
					  afterEach(() => {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
import archiver from 'archiver';
 | 
					import archiver from 'archiver';
 | 
				
			||||||
import chokidar, { WatchOptions } from 'chokidar';
 | 
					import chokidar, { WatchOptions } from 'chokidar';
 | 
				
			||||||
import { glob, globStream } from 'fast-glob';
 | 
					import { glob, globStream } from 'fast-glob';
 | 
				
			||||||
@ -5,6 +6,7 @@ import { constants, createReadStream, existsSync, mkdirSync } from 'node:fs';
 | 
				
			|||||||
import fs from 'node:fs/promises';
 | 
					import fs from 'node:fs/promises';
 | 
				
			||||||
import path from 'node:path';
 | 
					import path from 'node:path';
 | 
				
			||||||
import { CrawlOptionsDto } from 'src/dtos/library.dto';
 | 
					import { CrawlOptionsDto } from 'src/dtos/library.dto';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  DiskUsage,
 | 
					  DiskUsage,
 | 
				
			||||||
  IStorageRepository,
 | 
					  IStorageRepository,
 | 
				
			||||||
@ -13,12 +15,14 @@ import {
 | 
				
			|||||||
  WatchEvents,
 | 
					  WatchEvents,
 | 
				
			||||||
} from 'src/interfaces/storage.interface';
 | 
					} from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { Instrumentation } from 'src/utils/instrumentation';
 | 
					import { Instrumentation } from 'src/utils/instrumentation';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { mimeTypes } from 'src/utils/mime-types';
 | 
					import { mimeTypes } from 'src/utils/mime-types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Instrumentation()
 | 
					@Instrumentation()
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
export class StorageRepository implements IStorageRepository {
 | 
					export class StorageRepository implements IStorageRepository {
 | 
				
			||||||
  private logger = new ImmichLogger(StorageRepository.name);
 | 
					  constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
 | 
				
			||||||
 | 
					    this.logger.setContext(StorageRepository.name);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  readdir(folder: string): Promise<string[]> {
 | 
					  readdir(folder: string): Promise<string[]> {
 | 
				
			||||||
    return fs.readdir(folder);
 | 
					    return fs.readdir(folder);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
import { InjectRepository } from '@nestjs/typeorm';
 | 
					import { InjectRepository } from '@nestjs/typeorm';
 | 
				
			||||||
import { readFile } from 'node:fs/promises';
 | 
					import { readFile } from 'node:fs/promises';
 | 
				
			||||||
import { Chunked, DummyValue, GenerateSql } from 'src/decorators';
 | 
					import { Chunked, DummyValue, GenerateSql } from 'src/decorators';
 | 
				
			||||||
@ -7,6 +8,7 @@ import { Instrumentation } from 'src/utils/instrumentation';
 | 
				
			|||||||
import { In, Repository } from 'typeorm';
 | 
					import { In, Repository } from 'typeorm';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Instrumentation()
 | 
					@Instrumentation()
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
export class SystemConfigRepository implements ISystemConfigRepository {
 | 
					export class SystemConfigRepository implements ISystemConfigRepository {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @InjectRepository(SystemConfigEntity)
 | 
					    @InjectRepository(SystemConfigEntity)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
import { InjectRepository } from '@nestjs/typeorm';
 | 
					import { InjectRepository } from '@nestjs/typeorm';
 | 
				
			||||||
import { SystemMetadata, SystemMetadataEntity } from 'src/entities/system-metadata.entity';
 | 
					import { SystemMetadata, SystemMetadataEntity } from 'src/entities/system-metadata.entity';
 | 
				
			||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
 | 
					import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
 | 
				
			||||||
@ -5,6 +6,7 @@ import { Instrumentation } from 'src/utils/instrumentation';
 | 
				
			|||||||
import { Repository } from 'typeorm';
 | 
					import { Repository } from 'typeorm';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Instrumentation()
 | 
					@Instrumentation()
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
export class SystemMetadataRepository implements ISystemMetadataRepository {
 | 
					export class SystemMetadataRepository implements ISystemMetadataRepository {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @InjectRepository(SystemMetadataEntity)
 | 
					    @InjectRepository(SystemMetadataEntity)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,10 @@
 | 
				
			|||||||
import { Injectable } from '@nestjs/common';
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
import { Cron, CronExpression, Interval } from '@nestjs/schedule';
 | 
					import { Cron, CronExpression, Interval } from '@nestjs/schedule';
 | 
				
			||||||
import { NextFunction, Request, Response } from 'express';
 | 
					import { NextFunction, Request, Response } from 'express';
 | 
				
			||||||
import { readFileSync } from 'node:fs';
 | 
					import { readFileSync } from 'node:fs';
 | 
				
			||||||
import { join } from 'node:path';
 | 
					import { join } from 'node:path';
 | 
				
			||||||
import { ONE_HOUR, WEB_ROOT } from 'src/constants';
 | 
					import { ONE_HOUR, WEB_ROOT } from 'src/constants';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { AuthService } from 'src/services/auth.service';
 | 
					import { AuthService } from 'src/services/auth.service';
 | 
				
			||||||
import { DatabaseService } from 'src/services/database.service';
 | 
					import { DatabaseService } from 'src/services/database.service';
 | 
				
			||||||
import { JobService } from 'src/services/job.service';
 | 
					import { JobService } from 'src/services/job.service';
 | 
				
			||||||
@ -11,7 +12,6 @@ import { ServerInfoService } from 'src/services/server-info.service';
 | 
				
			|||||||
import { SharedLinkService } from 'src/services/shared-link.service';
 | 
					import { SharedLinkService } from 'src/services/shared-link.service';
 | 
				
			||||||
import { StorageService } from 'src/services/storage.service';
 | 
					import { StorageService } from 'src/services/storage.service';
 | 
				
			||||||
import { SystemConfigService } from 'src/services/system-config.service';
 | 
					import { SystemConfigService } from 'src/services/system-config.service';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { OpenGraphTags } from 'src/utils/misc';
 | 
					import { OpenGraphTags } from 'src/utils/misc';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const render = (index: string, meta: OpenGraphTags) => {
 | 
					const render = (index: string, meta: OpenGraphTags) => {
 | 
				
			||||||
@ -36,8 +36,6 @@ const render = (index: string, meta: OpenGraphTags) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class ApiService {
 | 
					export class ApiService {
 | 
				
			||||||
  private logger = new ImmichLogger(ApiService.name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private authService: AuthService,
 | 
					    private authService: AuthService,
 | 
				
			||||||
    private configService: SystemConfigService,
 | 
					    private configService: SystemConfigService,
 | 
				
			||||||
@ -46,7 +44,10 @@ export class ApiService {
 | 
				
			|||||||
    private sharedLinkService: SharedLinkService,
 | 
					    private sharedLinkService: SharedLinkService,
 | 
				
			||||||
    private storageService: StorageService,
 | 
					    private storageService: StorageService,
 | 
				
			||||||
    private databaseService: DatabaseService,
 | 
					    private databaseService: DatabaseService,
 | 
				
			||||||
  ) {}
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    this.logger.setContext(ApiService.name);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Interval(ONE_HOUR.as('milliseconds'))
 | 
					  @Interval(ONE_HOUR.as('milliseconds'))
 | 
				
			||||||
  async onVersionCheck() {
 | 
					  async onVersionCheck() {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import { IAssetRepositoryV1 } from 'src/interfaces/asset-v1.interface';
 | 
				
			|||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
					import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
				
			||||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
 | 
					import { IJobRepository, JobName } from 'src/interfaces/job.interface';
 | 
				
			||||||
import { ILibraryRepository } from 'src/interfaces/library.interface';
 | 
					import { ILibraryRepository } from 'src/interfaces/library.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { IUserRepository } from 'src/interfaces/user.interface';
 | 
					import { IUserRepository } from 'src/interfaces/user.interface';
 | 
				
			||||||
import { AssetServiceV1 } from 'src/services/asset-v1.service';
 | 
					import { AssetServiceV1 } from 'src/services/asset-v1.service';
 | 
				
			||||||
@ -16,6 +17,7 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie
 | 
				
			|||||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
					import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
				
			||||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
					import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
				
			||||||
import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock';
 | 
					import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
					import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
				
			||||||
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
 | 
					import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
 | 
				
			||||||
import { QueryFailedError } from 'typeorm';
 | 
					import { QueryFailedError } from 'typeorm';
 | 
				
			||||||
@ -66,6 +68,7 @@ describe('AssetService', () => {
 | 
				
			|||||||
  let assetMock: Mocked<IAssetRepository>;
 | 
					  let assetMock: Mocked<IAssetRepository>;
 | 
				
			||||||
  let jobMock: Mocked<IJobRepository>;
 | 
					  let jobMock: Mocked<IJobRepository>;
 | 
				
			||||||
  let libraryMock: Mocked<ILibraryRepository>;
 | 
					  let libraryMock: Mocked<ILibraryRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
  let storageMock: Mocked<IStorageRepository>;
 | 
					  let storageMock: Mocked<IStorageRepository>;
 | 
				
			||||||
  let userMock: Mocked<IUserRepository>;
 | 
					  let userMock: Mocked<IUserRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -85,10 +88,20 @@ describe('AssetService', () => {
 | 
				
			|||||||
    assetMock = newAssetRepositoryMock();
 | 
					    assetMock = newAssetRepositoryMock();
 | 
				
			||||||
    jobMock = newJobRepositoryMock();
 | 
					    jobMock = newJobRepositoryMock();
 | 
				
			||||||
    libraryMock = newLibraryRepositoryMock();
 | 
					    libraryMock = newLibraryRepositoryMock();
 | 
				
			||||||
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
    storageMock = newStorageRepositoryMock();
 | 
					    storageMock = newStorageRepositoryMock();
 | 
				
			||||||
    userMock = newUserRepositoryMock();
 | 
					    userMock = newUserRepositoryMock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sut = new AssetServiceV1(accessMock, assetRepositoryMockV1, assetMock, jobMock, libraryMock, storageMock, userMock);
 | 
					    sut = new AssetServiceV1(
 | 
				
			||||||
 | 
					      accessMock,
 | 
				
			||||||
 | 
					      assetRepositoryMockV1,
 | 
				
			||||||
 | 
					      assetMock,
 | 
				
			||||||
 | 
					      jobMock,
 | 
				
			||||||
 | 
					      libraryMock,
 | 
				
			||||||
 | 
					      storageMock,
 | 
				
			||||||
 | 
					      userMock,
 | 
				
			||||||
 | 
					      loggerMock,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assetRepositoryMockV1.get.mockImplementation((assetId) =>
 | 
					    assetRepositoryMockV1.get.mockImplementation((assetId) =>
 | 
				
			||||||
      Promise.resolve(
 | 
					      Promise.resolve(
 | 
				
			||||||
 | 
				
			|||||||
@ -33,18 +33,17 @@ import { IAssetRepositoryV1 } from 'src/interfaces/asset-v1.interface';
 | 
				
			|||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
					import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
				
			||||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
 | 
					import { IJobRepository, JobName } from 'src/interfaces/job.interface';
 | 
				
			||||||
import { ILibraryRepository } from 'src/interfaces/library.interface';
 | 
					import { ILibraryRepository } from 'src/interfaces/library.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { IUserRepository } from 'src/interfaces/user.interface';
 | 
					import { IUserRepository } from 'src/interfaces/user.interface';
 | 
				
			||||||
import { UploadFile } from 'src/services/asset.service';
 | 
					import { UploadFile } from 'src/services/asset.service';
 | 
				
			||||||
import { CacheControl, ImmichFileResponse, getLivePhotoMotionFilename } from 'src/utils/file';
 | 
					import { CacheControl, ImmichFileResponse, getLivePhotoMotionFilename } from 'src/utils/file';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { mimeTypes } from 'src/utils/mime-types';
 | 
					import { mimeTypes } from 'src/utils/mime-types';
 | 
				
			||||||
import { QueryFailedError } from 'typeorm';
 | 
					import { QueryFailedError } from 'typeorm';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
/** @deprecated */
 | 
					/** @deprecated */
 | 
				
			||||||
export class AssetServiceV1 {
 | 
					export class AssetServiceV1 {
 | 
				
			||||||
  readonly logger = new ImmichLogger(AssetServiceV1.name);
 | 
					 | 
				
			||||||
  private access: AccessCore;
 | 
					  private access: AccessCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
@ -55,8 +54,10 @@ export class AssetServiceV1 {
 | 
				
			|||||||
    @Inject(ILibraryRepository) private libraryRepository: ILibraryRepository,
 | 
					    @Inject(ILibraryRepository) private libraryRepository: ILibraryRepository,
 | 
				
			||||||
    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
					    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
				
			||||||
    @Inject(IUserRepository) private userRepository: IUserRepository,
 | 
					    @Inject(IUserRepository) private userRepository: IUserRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.access = AccessCore.create(accessRepository);
 | 
					    this.access = AccessCore.create(accessRepository);
 | 
				
			||||||
 | 
					    this.logger.setContext(AssetServiceV1.name);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async uploadFile(
 | 
					  public async uploadFile(
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface';
 | 
				
			|||||||
import { AssetStats, IAssetRepository } from 'src/interfaces/asset.interface';
 | 
					import { AssetStats, IAssetRepository } from 'src/interfaces/asset.interface';
 | 
				
			||||||
import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
 | 
					import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
 | 
				
			||||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
 | 
					import { IJobRepository, JobName } from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IPartnerRepository } from 'src/interfaces/partner.interface';
 | 
					import { IPartnerRepository } from 'src/interfaces/partner.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
@ -21,6 +22,7 @@ import { newAssetStackRepositoryMock } from 'test/repositories/asset-stack.repos
 | 
				
			|||||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
					import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
				
			||||||
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
 | 
					import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
 | 
				
			||||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
					import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock';
 | 
					import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock';
 | 
				
			||||||
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
					import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
				
			||||||
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
					import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
				
			||||||
@ -156,6 +158,7 @@ describe(AssetService.name, () => {
 | 
				
			|||||||
  let configMock: Mocked<ISystemConfigRepository>;
 | 
					  let configMock: Mocked<ISystemConfigRepository>;
 | 
				
			||||||
  let partnerMock: Mocked<IPartnerRepository>;
 | 
					  let partnerMock: Mocked<IPartnerRepository>;
 | 
				
			||||||
  let assetStackMock: Mocked<IAssetStackRepository>;
 | 
					  let assetStackMock: Mocked<IAssetStackRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should work', () => {
 | 
					  it('should work', () => {
 | 
				
			||||||
    expect(sut).toBeDefined();
 | 
					    expect(sut).toBeDefined();
 | 
				
			||||||
@ -177,6 +180,7 @@ describe(AssetService.name, () => {
 | 
				
			|||||||
    configMock = newSystemConfigRepositoryMock();
 | 
					    configMock = newSystemConfigRepositoryMock();
 | 
				
			||||||
    partnerMock = newPartnerRepositoryMock();
 | 
					    partnerMock = newPartnerRepositoryMock();
 | 
				
			||||||
    assetStackMock = newAssetStackRepositoryMock();
 | 
					    assetStackMock = newAssetStackRepositoryMock();
 | 
				
			||||||
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sut = new AssetService(
 | 
					    sut = new AssetService(
 | 
				
			||||||
      accessMock,
 | 
					      accessMock,
 | 
				
			||||||
@ -188,6 +192,7 @@ describe(AssetService.name, () => {
 | 
				
			|||||||
      eventMock,
 | 
					      eventMock,
 | 
				
			||||||
      partnerMock,
 | 
					      partnerMock,
 | 
				
			||||||
      assetStackMock,
 | 
					      assetStackMock,
 | 
				
			||||||
 | 
					      loggerMock,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    mockGetById([assetStub.livePhotoStillAsset, assetStub.livePhotoMotionAsset]);
 | 
					    mockGetById([assetStub.livePhotoStillAsset, assetStub.livePhotoMotionAsset]);
 | 
				
			||||||
 | 
				
			|||||||
@ -40,11 +40,11 @@ import {
 | 
				
			|||||||
  JobName,
 | 
					  JobName,
 | 
				
			||||||
  JobStatus,
 | 
					  JobStatus,
 | 
				
			||||||
} from 'src/interfaces/job.interface';
 | 
					} from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IPartnerRepository } from 'src/interfaces/partner.interface';
 | 
					import { IPartnerRepository } from 'src/interfaces/partner.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { IUserRepository } from 'src/interfaces/user.interface';
 | 
					import { IUserRepository } from 'src/interfaces/user.interface';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { mimeTypes } from 'src/utils/mime-types';
 | 
					import { mimeTypes } from 'src/utils/mime-types';
 | 
				
			||||||
import { usePagination } from 'src/utils/pagination';
 | 
					import { usePagination } from 'src/utils/pagination';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -63,7 +63,6 @@ export interface UploadFile {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class AssetService {
 | 
					export class AssetService {
 | 
				
			||||||
  private logger = new ImmichLogger(AssetService.name);
 | 
					 | 
				
			||||||
  private access: AccessCore;
 | 
					  private access: AccessCore;
 | 
				
			||||||
  private configCore: SystemConfigCore;
 | 
					  private configCore: SystemConfigCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -77,9 +76,11 @@ export class AssetService {
 | 
				
			|||||||
    @Inject(IEventRepository) private eventRepository: IEventRepository,
 | 
					    @Inject(IEventRepository) private eventRepository: IEventRepository,
 | 
				
			||||||
    @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
 | 
					    @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
 | 
				
			||||||
    @Inject(IAssetStackRepository) private assetStackRepository: IAssetStackRepository,
 | 
					    @Inject(IAssetStackRepository) private assetStackRepository: IAssetStackRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
 | 
					    this.logger.setContext(AssetService.name);
 | 
				
			||||||
    this.access = AccessCore.create(accessRepository);
 | 
					    this.access = AccessCore.create(accessRepository);
 | 
				
			||||||
    this.configCore = SystemConfigCore.create(configRepository);
 | 
					    this.configCore = SystemConfigCore.create(configRepository, this.logger);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  canUploadFile({ auth, fieldName, file }: UploadRequest): true {
 | 
					  canUploadFile({ auth, fieldName, file }: UploadRequest): true {
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
				
			|||||||
import { IAuditRepository } from 'src/interfaces/audit.interface';
 | 
					import { IAuditRepository } from 'src/interfaces/audit.interface';
 | 
				
			||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
					import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
				
			||||||
import { JobStatus } from 'src/interfaces/job.interface';
 | 
					import { JobStatus } from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
					import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { IUserRepository } from 'src/interfaces/user.interface';
 | 
					import { IUserRepository } from 'src/interfaces/user.interface';
 | 
				
			||||||
@ -13,6 +14,7 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie
 | 
				
			|||||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
					import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
				
			||||||
import { newAuditRepositoryMock } from 'test/repositories/audit.repository.mock';
 | 
					import { newAuditRepositoryMock } from 'test/repositories/audit.repository.mock';
 | 
				
			||||||
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
 | 
					import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
 | 
					import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
 | 
				
			||||||
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
					import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
				
			||||||
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
 | 
					import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
 | 
				
			||||||
@ -27,6 +29,7 @@ describe(AuditService.name, () => {
 | 
				
			|||||||
  let personMock: Mocked<IPersonRepository>;
 | 
					  let personMock: Mocked<IPersonRepository>;
 | 
				
			||||||
  let storageMock: Mocked<IStorageRepository>;
 | 
					  let storageMock: Mocked<IStorageRepository>;
 | 
				
			||||||
  let userMock: Mocked<IUserRepository>;
 | 
					  let userMock: Mocked<IUserRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    accessMock = newAccessRepositoryMock();
 | 
					    accessMock = newAccessRepositoryMock();
 | 
				
			||||||
@ -36,7 +39,8 @@ describe(AuditService.name, () => {
 | 
				
			|||||||
    personMock = newPersonRepositoryMock();
 | 
					    personMock = newPersonRepositoryMock();
 | 
				
			||||||
    storageMock = newStorageRepositoryMock();
 | 
					    storageMock = newStorageRepositoryMock();
 | 
				
			||||||
    userMock = newUserRepositoryMock();
 | 
					    userMock = newUserRepositoryMock();
 | 
				
			||||||
    sut = new AuditService(accessMock, assetMock, cryptoMock, personMock, auditMock, storageMock, userMock);
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
 | 
					    sut = new AuditService(accessMock, assetMock, cryptoMock, personMock, auditMock, storageMock, userMock, loggerMock);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should work', () => {
 | 
					  it('should work', () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -20,16 +20,15 @@ import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
				
			|||||||
import { IAuditRepository } from 'src/interfaces/audit.interface';
 | 
					import { IAuditRepository } from 'src/interfaces/audit.interface';
 | 
				
			||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
					import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
				
			||||||
import { JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.interface';
 | 
					import { JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
					import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { IUserRepository } from 'src/interfaces/user.interface';
 | 
					import { IUserRepository } from 'src/interfaces/user.interface';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { usePagination } from 'src/utils/pagination';
 | 
					import { usePagination } from 'src/utils/pagination';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class AuditService {
 | 
					export class AuditService {
 | 
				
			||||||
  private access: AccessCore;
 | 
					  private access: AccessCore;
 | 
				
			||||||
  private logger = new ImmichLogger(AuditService.name);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @Inject(IAccessRepository) accessRepository: IAccessRepository,
 | 
					    @Inject(IAccessRepository) accessRepository: IAccessRepository,
 | 
				
			||||||
@ -39,8 +38,10 @@ export class AuditService {
 | 
				
			|||||||
    @Inject(IAuditRepository) private repository: IAuditRepository,
 | 
					    @Inject(IAuditRepository) private repository: IAuditRepository,
 | 
				
			||||||
    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
					    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
				
			||||||
    @Inject(IUserRepository) private userRepository: IUserRepository,
 | 
					    @Inject(IUserRepository) private userRepository: IUserRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.access = AccessCore.create(accessRepository);
 | 
					    this.access = AccessCore.create(accessRepository);
 | 
				
			||||||
 | 
					    this.logger.setContext(AuditService.name);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async handleCleanup(): Promise<JobStatus> {
 | 
					  async handleCleanup(): Promise<JobStatus> {
 | 
				
			||||||
 | 
				
			|||||||
@ -89,10 +89,10 @@ export class AuthService {
 | 
				
			|||||||
    @Inject(ISharedLinkRepository) private sharedLinkRepository: ISharedLinkRepository,
 | 
					    @Inject(ISharedLinkRepository) private sharedLinkRepository: ISharedLinkRepository,
 | 
				
			||||||
    @Inject(IKeyRepository) private keyRepository: IKeyRepository,
 | 
					    @Inject(IKeyRepository) private keyRepository: IKeyRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.access = AccessCore.create(accessRepository);
 | 
					 | 
				
			||||||
    this.configCore = SystemConfigCore.create(configRepository);
 | 
					 | 
				
			||||||
    this.userCore = UserCore.create(cryptoRepository, libraryRepository, userRepository);
 | 
					 | 
				
			||||||
    this.logger.setContext(AuthService.name);
 | 
					    this.logger.setContext(AuthService.name);
 | 
				
			||||||
 | 
					    this.access = AccessCore.create(accessRepository);
 | 
				
			||||||
 | 
					    this.configCore = SystemConfigCore.create(configRepository, logger);
 | 
				
			||||||
 | 
					    this.userCore = UserCore.create(cryptoRepository, libraryRepository, userRepository);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    custom.setHttpOptionsDefaults({ timeout: 30_000 });
 | 
					    custom.setHttpOptionsDefaults({ timeout: 30_000 });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,18 +1,20 @@
 | 
				
			|||||||
import { DatabaseExtension, IDatabaseRepository, VectorIndex } from 'src/interfaces/database.interface';
 | 
					import { DatabaseExtension, IDatabaseRepository, VectorIndex } from 'src/interfaces/database.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { DatabaseService } from 'src/services/database.service';
 | 
					import { DatabaseService } from 'src/services/database.service';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { Version, VersionType } from 'src/utils/version';
 | 
					import { Version, VersionType } from 'src/utils/version';
 | 
				
			||||||
import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
 | 
					import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
 | 
				
			||||||
import { MockInstance, Mocked, vitest } from 'vitest';
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
 | 
					import { Mocked } from 'vitest';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe(DatabaseService.name, () => {
 | 
					describe(DatabaseService.name, () => {
 | 
				
			||||||
  let sut: DatabaseService;
 | 
					  let sut: DatabaseService;
 | 
				
			||||||
  let databaseMock: Mocked<IDatabaseRepository>;
 | 
					  let databaseMock: Mocked<IDatabaseRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    databaseMock = newDatabaseRepositoryMock();
 | 
					    databaseMock = newDatabaseRepositoryMock();
 | 
				
			||||||
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
    sut = new DatabaseService(databaseMock);
 | 
					    sut = new DatabaseService(databaseMock, loggerMock);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should work', () => {
 | 
					  it('should work', () => {
 | 
				
			||||||
@ -23,18 +25,11 @@ describe(DatabaseService.name, () => {
 | 
				
			|||||||
    [{ vectorExt: DatabaseExtension.VECTORS, extName: 'pgvecto.rs', minVersion: new Version(0, 1, 1) }],
 | 
					    [{ vectorExt: DatabaseExtension.VECTORS, extName: 'pgvecto.rs', minVersion: new Version(0, 1, 1) }],
 | 
				
			||||||
    [{ vectorExt: DatabaseExtension.VECTOR, extName: 'pgvector', minVersion: new Version(0, 5, 0) }],
 | 
					    [{ vectorExt: DatabaseExtension.VECTOR, extName: 'pgvector', minVersion: new Version(0, 5, 0) }],
 | 
				
			||||||
  ] as const)('init', ({ vectorExt, extName, minVersion }) => {
 | 
					  ] as const)('init', ({ vectorExt, extName, minVersion }) => {
 | 
				
			||||||
    let fatalLog: MockInstance;
 | 
					 | 
				
			||||||
    let errorLog: MockInstance;
 | 
					 | 
				
			||||||
    let warnLog: MockInstance;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    beforeEach(() => {
 | 
					    beforeEach(() => {
 | 
				
			||||||
      fatalLog = vitest.spyOn(ImmichLogger.prototype, 'fatal');
 | 
					 | 
				
			||||||
      errorLog = vitest.spyOn(ImmichLogger.prototype, 'error');
 | 
					 | 
				
			||||||
      warnLog = vitest.spyOn(ImmichLogger.prototype, 'warn');
 | 
					 | 
				
			||||||
      databaseMock.getPreferredVectorExtension.mockReturnValue(vectorExt);
 | 
					      databaseMock.getPreferredVectorExtension.mockReturnValue(vectorExt);
 | 
				
			||||||
      databaseMock.getExtensionVersion.mockResolvedValue(minVersion);
 | 
					      databaseMock.getExtensionVersion.mockResolvedValue(minVersion);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      sut = new DatabaseService(databaseMock);
 | 
					      sut = new DatabaseService(databaseMock, loggerMock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      sut.minVectorVersion = minVersion;
 | 
					      sut.minVectorVersion = minVersion;
 | 
				
			||||||
      sut.minVectorsVersion = minVersion;
 | 
					      sut.minVectorsVersion = minVersion;
 | 
				
			||||||
@ -42,11 +37,6 @@ describe(DatabaseService.name, () => {
 | 
				
			|||||||
      sut.vectorsVersionPin = VersionType.MINOR;
 | 
					      sut.vectorsVersionPin = VersionType.MINOR;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    afterEach(() => {
 | 
					 | 
				
			||||||
      fatalLog.mockRestore();
 | 
					 | 
				
			||||||
      warnLog.mockRestore();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it(`should resolve successfully if minimum supported PostgreSQL and ${extName} version are installed`, async () => {
 | 
					    it(`should resolve successfully if minimum supported PostgreSQL and ${extName} version are installed`, async () => {
 | 
				
			||||||
      databaseMock.getPostgresVersion.mockResolvedValueOnce(new Version(14, 0, 0));
 | 
					      databaseMock.getPostgresVersion.mockResolvedValueOnce(new Version(14, 0, 0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -57,7 +47,7 @@ describe(DatabaseService.name, () => {
 | 
				
			|||||||
      expect(databaseMock.createExtension).toHaveBeenCalledTimes(1);
 | 
					      expect(databaseMock.createExtension).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      expect(databaseMock.getExtensionVersion).toHaveBeenCalled();
 | 
					      expect(databaseMock.getExtensionVersion).toHaveBeenCalled();
 | 
				
			||||||
      expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
					      expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      expect(fatalLog).not.toHaveBeenCalled();
 | 
					      expect(loggerMock.fatal).not.toHaveBeenCalled();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('should throw an error if PostgreSQL version is below minimum supported version', async () => {
 | 
					    it('should throw an error if PostgreSQL version is below minimum supported version', async () => {
 | 
				
			||||||
@ -74,7 +64,7 @@ describe(DatabaseService.name, () => {
 | 
				
			|||||||
      expect(databaseMock.createExtension).toHaveBeenCalledWith(vectorExt);
 | 
					      expect(databaseMock.createExtension).toHaveBeenCalledWith(vectorExt);
 | 
				
			||||||
      expect(databaseMock.createExtension).toHaveBeenCalledTimes(1);
 | 
					      expect(databaseMock.createExtension).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
					      expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      expect(fatalLog).not.toHaveBeenCalled();
 | 
					      expect(loggerMock.fatal).not.toHaveBeenCalled();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it(`should throw an error if ${extName} version is not installed even after createVectorExtension`, async () => {
 | 
					    it(`should throw an error if ${extName} version is not installed even after createVectorExtension`, async () => {
 | 
				
			||||||
@ -134,7 +124,7 @@ describe(DatabaseService.name, () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      await expect(sut.init()).rejects.toThrow('Failed to create extension');
 | 
					      await expect(sut.init()).rejects.toThrow('Failed to create extension');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(fatalLog).toHaveBeenCalledTimes(1);
 | 
					      expect(loggerMock.fatal).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      expect(databaseMock.createExtension).toHaveBeenCalledTimes(1);
 | 
					      expect(databaseMock.createExtension).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      expect(databaseMock.runMigrations).not.toHaveBeenCalled();
 | 
					      expect(databaseMock.runMigrations).not.toHaveBeenCalled();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@ -148,7 +138,7 @@ describe(DatabaseService.name, () => {
 | 
				
			|||||||
      expect(databaseMock.updateVectorExtension).toHaveBeenCalledWith(vectorExt, version);
 | 
					      expect(databaseMock.updateVectorExtension).toHaveBeenCalledWith(vectorExt, version);
 | 
				
			||||||
      expect(databaseMock.updateVectorExtension).toHaveBeenCalledTimes(1);
 | 
					      expect(databaseMock.updateVectorExtension).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
					      expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      expect(fatalLog).not.toHaveBeenCalled();
 | 
					      expect(loggerMock.fatal).not.toHaveBeenCalled();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it(`should not update ${extName} if a newer version is higher than the maximum`, async () => {
 | 
					    it(`should not update ${extName} if a newer version is higher than the maximum`, async () => {
 | 
				
			||||||
@ -159,7 +149,7 @@ describe(DatabaseService.name, () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      expect(databaseMock.updateVectorExtension).not.toHaveBeenCalled();
 | 
					      expect(databaseMock.updateVectorExtension).not.toHaveBeenCalled();
 | 
				
			||||||
      expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
					      expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      expect(fatalLog).not.toHaveBeenCalled();
 | 
					      expect(loggerMock.fatal).not.toHaveBeenCalled();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it(`should warn if attempted to update ${extName} and failed`, async () => {
 | 
					    it(`should warn if attempted to update ${extName} and failed`, async () => {
 | 
				
			||||||
@ -169,10 +159,10 @@ describe(DatabaseService.name, () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      await expect(sut.init()).resolves.toBeUndefined();
 | 
					      await expect(sut.init()).resolves.toBeUndefined();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(warnLog).toHaveBeenCalledTimes(1);
 | 
					      expect(loggerMock.warn).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      expect(warnLog.mock.calls[0][0]).toContain(extName);
 | 
					      expect(loggerMock.warn.mock.calls[0][0]).toContain(extName);
 | 
				
			||||||
      expect(errorLog).toHaveBeenCalledTimes(1);
 | 
					      expect(loggerMock.error).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      expect(fatalLog).not.toHaveBeenCalled();
 | 
					      expect(loggerMock.fatal).not.toHaveBeenCalled();
 | 
				
			||||||
      expect(databaseMock.updateVectorExtension).toHaveBeenCalledWith(vectorExt, version);
 | 
					      expect(databaseMock.updateVectorExtension).toHaveBeenCalledWith(vectorExt, version);
 | 
				
			||||||
      expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
					      expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@ -184,11 +174,11 @@ describe(DatabaseService.name, () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      await expect(sut.init()).resolves.toBeUndefined();
 | 
					      await expect(sut.init()).resolves.toBeUndefined();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(warnLog).toHaveBeenCalledTimes(1);
 | 
					      expect(loggerMock.warn).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      expect(warnLog.mock.calls[0][0]).toContain(extName);
 | 
					      expect(loggerMock.warn.mock.calls[0][0]).toContain(extName);
 | 
				
			||||||
      expect(databaseMock.updateVectorExtension).toHaveBeenCalledWith(vectorExt, version);
 | 
					      expect(databaseMock.updateVectorExtension).toHaveBeenCalledWith(vectorExt, version);
 | 
				
			||||||
      expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
					      expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      expect(fatalLog).not.toHaveBeenCalled();
 | 
					      expect(loggerMock.fatal).not.toHaveBeenCalled();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it.each([{ index: VectorIndex.CLIP }, { index: VectorIndex.FACE }])(
 | 
					    it.each([{ index: VectorIndex.CLIP }, { index: VectorIndex.FACE }])(
 | 
				
			||||||
@ -203,7 +193,7 @@ describe(DatabaseService.name, () => {
 | 
				
			|||||||
        expect(databaseMock.reindex).toHaveBeenCalledWith(index);
 | 
					        expect(databaseMock.reindex).toHaveBeenCalledWith(index);
 | 
				
			||||||
        expect(databaseMock.reindex).toHaveBeenCalledTimes(1);
 | 
					        expect(databaseMock.reindex).toHaveBeenCalledTimes(1);
 | 
				
			||||||
        expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
					        expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
				
			||||||
        expect(fatalLog).not.toHaveBeenCalled();
 | 
					        expect(loggerMock.fatal).not.toHaveBeenCalled();
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -217,7 +207,7 @@ describe(DatabaseService.name, () => {
 | 
				
			|||||||
        expect(databaseMock.shouldReindex).toHaveBeenCalledTimes(2);
 | 
					        expect(databaseMock.shouldReindex).toHaveBeenCalledTimes(2);
 | 
				
			||||||
        expect(databaseMock.reindex).not.toHaveBeenCalled();
 | 
					        expect(databaseMock.reindex).not.toHaveBeenCalled();
 | 
				
			||||||
        expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
					        expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
 | 
				
			||||||
        expect(fatalLog).not.toHaveBeenCalled();
 | 
					        expect(loggerMock.fatal).not.toHaveBeenCalled();
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
				
			|||||||
@ -7,12 +7,11 @@ import {
 | 
				
			|||||||
  VectorIndex,
 | 
					  VectorIndex,
 | 
				
			||||||
  extName,
 | 
					  extName,
 | 
				
			||||||
} from 'src/interfaces/database.interface';
 | 
					} from 'src/interfaces/database.interface';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { Version, VersionType } from 'src/utils/version';
 | 
					import { Version, VersionType } from 'src/utils/version';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class DatabaseService {
 | 
					export class DatabaseService {
 | 
				
			||||||
  private logger = new ImmichLogger(DatabaseService.name);
 | 
					 | 
				
			||||||
  private vectorExt: VectorExtension;
 | 
					  private vectorExt: VectorExtension;
 | 
				
			||||||
  minPostgresVersion = 14;
 | 
					  minPostgresVersion = 14;
 | 
				
			||||||
  minVectorsVersion = new Version(0, 2, 0);
 | 
					  minVectorsVersion = new Version(0, 2, 0);
 | 
				
			||||||
@ -20,7 +19,11 @@ export class DatabaseService {
 | 
				
			|||||||
  minVectorVersion = new Version(0, 5, 0);
 | 
					  minVectorVersion = new Version(0, 5, 0);
 | 
				
			||||||
  vectorVersionPin = VersionType.MAJOR;
 | 
					  vectorVersionPin = VersionType.MAJOR;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(@Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository) {
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    this.logger.setContext(DatabaseService.name);
 | 
				
			||||||
    this.vectorExt = this.databaseRepository.getPreferredVectorExtension();
 | 
					    this.vectorExt = this.databaseRepository.getPreferredVectorExtension();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ import {
 | 
				
			|||||||
  JobStatus,
 | 
					  JobStatus,
 | 
				
			||||||
  QueueName,
 | 
					  QueueName,
 | 
				
			||||||
} from 'src/interfaces/job.interface';
 | 
					} from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMetricRepository } from 'src/interfaces/metric.interface';
 | 
					import { IMetricRepository } from 'src/interfaces/metric.interface';
 | 
				
			||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
					import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
@ -20,6 +21,7 @@ import { assetStub } from 'test/fixtures/asset.stub';
 | 
				
			|||||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
					import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
				
			||||||
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
 | 
					import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
 | 
				
			||||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
					import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newMetricRepositoryMock } from 'test/repositories/metric.repository.mock';
 | 
					import { newMetricRepositoryMock } from 'test/repositories/metric.repository.mock';
 | 
				
			||||||
import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
 | 
					import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
 | 
				
			||||||
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
					import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
				
			||||||
@ -41,6 +43,7 @@ describe(JobService.name, () => {
 | 
				
			|||||||
  let jobMock: Mocked<IJobRepository>;
 | 
					  let jobMock: Mocked<IJobRepository>;
 | 
				
			||||||
  let personMock: Mocked<IPersonRepository>;
 | 
					  let personMock: Mocked<IPersonRepository>;
 | 
				
			||||||
  let metricMock: Mocked<IMetricRepository>;
 | 
					  let metricMock: Mocked<IMetricRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    assetMock = newAssetRepositoryMock();
 | 
					    assetMock = newAssetRepositoryMock();
 | 
				
			||||||
@ -49,7 +52,8 @@ describe(JobService.name, () => {
 | 
				
			|||||||
    jobMock = newJobRepositoryMock();
 | 
					    jobMock = newJobRepositoryMock();
 | 
				
			||||||
    personMock = newPersonRepositoryMock();
 | 
					    personMock = newPersonRepositoryMock();
 | 
				
			||||||
    metricMock = newMetricRepositoryMock();
 | 
					    metricMock = newMetricRepositoryMock();
 | 
				
			||||||
    sut = new JobService(assetMock, eventMock, jobMock, configMock, personMock, metricMock);
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
 | 
					    sut = new JobService(assetMock, eventMock, jobMock, configMock, personMock, metricMock, loggerMock);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should work', () => {
 | 
					  it('should work', () => {
 | 
				
			||||||
@ -235,7 +239,7 @@ describe(JobService.name, () => {
 | 
				
			|||||||
    it('should subscribe to config changes', async () => {
 | 
					    it('should subscribe to config changes', async () => {
 | 
				
			||||||
      await sut.init(makeMockHandlers(JobStatus.FAILED));
 | 
					      await sut.init(makeMockHandlers(JobStatus.FAILED));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      SystemConfigCore.create(newSystemConfigRepositoryMock(false)).config$.next({
 | 
					      SystemConfigCore.create(newSystemConfigRepositoryMock(false), newLoggerRepositoryMock()).config$.next({
 | 
				
			||||||
        job: {
 | 
					        job: {
 | 
				
			||||||
          [QueueName.BACKGROUND_TASK]: { concurrency: 10 },
 | 
					          [QueueName.BACKGROUND_TASK]: { concurrency: 10 },
 | 
				
			||||||
          [QueueName.SMART_SEARCH]: { concurrency: 10 },
 | 
					          [QueueName.SMART_SEARCH]: { concurrency: 10 },
 | 
				
			||||||
 | 
				
			|||||||
@ -17,14 +17,13 @@ import {
 | 
				
			|||||||
  QueueCleanType,
 | 
					  QueueCleanType,
 | 
				
			||||||
  QueueName,
 | 
					  QueueName,
 | 
				
			||||||
} from 'src/interfaces/job.interface';
 | 
					} from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMetricRepository } from 'src/interfaces/metric.interface';
 | 
					import { IMetricRepository } from 'src/interfaces/metric.interface';
 | 
				
			||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
					import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class JobService {
 | 
					export class JobService {
 | 
				
			||||||
  private logger = new ImmichLogger(JobService.name);
 | 
					 | 
				
			||||||
  private configCore: SystemConfigCore;
 | 
					  private configCore: SystemConfigCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
@ -34,8 +33,10 @@ export class JobService {
 | 
				
			|||||||
    @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
 | 
					    @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
 | 
				
			||||||
    @Inject(IPersonRepository) private personRepository: IPersonRepository,
 | 
					    @Inject(IPersonRepository) private personRepository: IPersonRepository,
 | 
				
			||||||
    @Inject(IMetricRepository) private metricRepository: IMetricRepository,
 | 
					    @Inject(IMetricRepository) private metricRepository: IMetricRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.configCore = SystemConfigCore.create(configRepository);
 | 
					    this.logger.setContext(JobService.name);
 | 
				
			||||||
 | 
					    this.configCore = SystemConfigCore.create(configRepository, logger);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async handleCommand(queueName: QueueName, dto: JobCommandDto): Promise<JobStatusDto> {
 | 
					  async handleCommand(queueName: QueueName, dto: JobCommandDto): Promise<JobStatusDto> {
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,7 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
				
			|||||||
import { IDatabaseRepository } from 'src/interfaces/database.interface';
 | 
					import { IDatabaseRepository } from 'src/interfaces/database.interface';
 | 
				
			||||||
import { IJobRepository, ILibraryFileJob, ILibraryRefreshJob, JobName, JobStatus } from 'src/interfaces/job.interface';
 | 
					import { IJobRepository, ILibraryFileJob, ILibraryRefreshJob, JobName, JobStatus } from 'src/interfaces/job.interface';
 | 
				
			||||||
import { ILibraryRepository } from 'src/interfaces/library.interface';
 | 
					import { ILibraryRepository } from 'src/interfaces/library.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { LibraryService } from 'src/services/library.service';
 | 
					import { LibraryService } from 'src/services/library.service';
 | 
				
			||||||
@ -24,6 +25,7 @@ import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.moc
 | 
				
			|||||||
import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
 | 
					import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
 | 
				
			||||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
					import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
				
			||||||
import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock';
 | 
					import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { makeMockWatcher, newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
					import { makeMockWatcher, newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
				
			||||||
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
					import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
				
			||||||
import { Mocked, vitest } from 'vitest';
 | 
					import { Mocked, vitest } from 'vitest';
 | 
				
			||||||
@ -38,6 +40,7 @@ describe(LibraryService.name, () => {
 | 
				
			|||||||
  let libraryMock: Mocked<ILibraryRepository>;
 | 
					  let libraryMock: Mocked<ILibraryRepository>;
 | 
				
			||||||
  let storageMock: Mocked<IStorageRepository>;
 | 
					  let storageMock: Mocked<IStorageRepository>;
 | 
				
			||||||
  let databaseMock: Mocked<IDatabaseRepository>;
 | 
					  let databaseMock: Mocked<IDatabaseRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    configMock = newSystemConfigRepositoryMock();
 | 
					    configMock = newSystemConfigRepositoryMock();
 | 
				
			||||||
@ -47,8 +50,18 @@ describe(LibraryService.name, () => {
 | 
				
			|||||||
    cryptoMock = newCryptoRepositoryMock();
 | 
					    cryptoMock = newCryptoRepositoryMock();
 | 
				
			||||||
    storageMock = newStorageRepositoryMock();
 | 
					    storageMock = newStorageRepositoryMock();
 | 
				
			||||||
    databaseMock = newDatabaseRepositoryMock();
 | 
					    databaseMock = newDatabaseRepositoryMock();
 | 
				
			||||||
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sut = new LibraryService(assetMock, configMock, cryptoMock, jobMock, libraryMock, storageMock, databaseMock);
 | 
					    sut = new LibraryService(
 | 
				
			||||||
 | 
					      assetMock,
 | 
				
			||||||
 | 
					      configMock,
 | 
				
			||||||
 | 
					      cryptoMock,
 | 
				
			||||||
 | 
					      jobMock,
 | 
				
			||||||
 | 
					      libraryMock,
 | 
				
			||||||
 | 
					      storageMock,
 | 
				
			||||||
 | 
					      databaseMock,
 | 
				
			||||||
 | 
					      loggerMock,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    databaseMock.tryLock.mockResolvedValue(true);
 | 
					    databaseMock.tryLock.mockResolvedValue(true);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
@ -68,7 +81,7 @@ describe(LibraryService.name, () => {
 | 
				
			|||||||
      expect(configMock.load).toHaveBeenCalled();
 | 
					      expect(configMock.load).toHaveBeenCalled();
 | 
				
			||||||
      expect(jobMock.addCronJob).toHaveBeenCalled();
 | 
					      expect(jobMock.addCronJob).toHaveBeenCalled();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      SystemConfigCore.create(newSystemConfigRepositoryMock(false)).config$.next({
 | 
					      SystemConfigCore.create(newSystemConfigRepositoryMock(false), newLoggerRepositoryMock()).config$.next({
 | 
				
			||||||
        library: {
 | 
					        library: {
 | 
				
			||||||
          scan: {
 | 
					          scan: {
 | 
				
			||||||
            enabled: true,
 | 
					            enabled: true,
 | 
				
			||||||
 | 
				
			|||||||
@ -36,9 +36,9 @@ import {
 | 
				
			|||||||
  JobStatus,
 | 
					  JobStatus,
 | 
				
			||||||
} from 'src/interfaces/job.interface';
 | 
					} from 'src/interfaces/job.interface';
 | 
				
			||||||
import { ILibraryRepository } from 'src/interfaces/library.interface';
 | 
					import { ILibraryRepository } from 'src/interfaces/library.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { mimeTypes } from 'src/utils/mime-types';
 | 
					import { mimeTypes } from 'src/utils/mime-types';
 | 
				
			||||||
import { handlePromiseError } from 'src/utils/misc';
 | 
					import { handlePromiseError } from 'src/utils/misc';
 | 
				
			||||||
import { usePagination } from 'src/utils/pagination';
 | 
					import { usePagination } from 'src/utils/pagination';
 | 
				
			||||||
@ -48,7 +48,6 @@ const LIBRARY_SCAN_BATCH_SIZE = 5000;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class LibraryService {
 | 
					export class LibraryService {
 | 
				
			||||||
  readonly logger = new ImmichLogger(LibraryService.name);
 | 
					 | 
				
			||||||
  private configCore: SystemConfigCore;
 | 
					  private configCore: SystemConfigCore;
 | 
				
			||||||
  private watchLibraries = false;
 | 
					  private watchLibraries = false;
 | 
				
			||||||
  private watchLock = false;
 | 
					  private watchLock = false;
 | 
				
			||||||
@ -62,8 +61,10 @@ export class LibraryService {
 | 
				
			|||||||
    @Inject(ILibraryRepository) private repository: ILibraryRepository,
 | 
					    @Inject(ILibraryRepository) private repository: ILibraryRepository,
 | 
				
			||||||
    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
					    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
				
			||||||
    @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository,
 | 
					    @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.configCore = SystemConfigCore.create(configRepository);
 | 
					    this.logger.setContext(LibraryService.name);
 | 
				
			||||||
 | 
					    this.configCore = SystemConfigCore.create(configRepository, this.logger);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async init() {
 | 
					  async init() {
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,7 @@ import {
 | 
				
			|||||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
 | 
					import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
 | 
				
			||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
					import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
				
			||||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
 | 
					import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMediaRepository } from 'src/interfaces/media.interface';
 | 
					import { IMediaRepository } from 'src/interfaces/media.interface';
 | 
				
			||||||
import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
					import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
				
			||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
					import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
				
			||||||
@ -27,6 +28,7 @@ import { personStub } from 'test/fixtures/person.stub';
 | 
				
			|||||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
					import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
				
			||||||
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
 | 
					import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
 | 
				
			||||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
					import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock';
 | 
					import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock';
 | 
				
			||||||
import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock';
 | 
					import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock';
 | 
				
			||||||
import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
 | 
					import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
 | 
				
			||||||
@ -44,6 +46,7 @@ describe(MediaService.name, () => {
 | 
				
			|||||||
  let personMock: Mocked<IPersonRepository>;
 | 
					  let personMock: Mocked<IPersonRepository>;
 | 
				
			||||||
  let storageMock: Mocked<IStorageRepository>;
 | 
					  let storageMock: Mocked<IStorageRepository>;
 | 
				
			||||||
  let cryptoMock: Mocked<ICryptoRepository>;
 | 
					  let cryptoMock: Mocked<ICryptoRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    assetMock = newAssetRepositoryMock();
 | 
					    assetMock = newAssetRepositoryMock();
 | 
				
			||||||
@ -54,8 +57,19 @@ describe(MediaService.name, () => {
 | 
				
			|||||||
    personMock = newPersonRepositoryMock();
 | 
					    personMock = newPersonRepositoryMock();
 | 
				
			||||||
    storageMock = newStorageRepositoryMock();
 | 
					    storageMock = newStorageRepositoryMock();
 | 
				
			||||||
    cryptoMock = newCryptoRepositoryMock();
 | 
					    cryptoMock = newCryptoRepositoryMock();
 | 
				
			||||||
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sut = new MediaService(assetMock, personMock, jobMock, mediaMock, storageMock, configMock, moveMock, cryptoMock);
 | 
					    sut = new MediaService(
 | 
				
			||||||
 | 
					      assetMock,
 | 
				
			||||||
 | 
					      personMock,
 | 
				
			||||||
 | 
					      jobMock,
 | 
				
			||||||
 | 
					      mediaMock,
 | 
				
			||||||
 | 
					      storageMock,
 | 
				
			||||||
 | 
					      configMock,
 | 
				
			||||||
 | 
					      moveMock,
 | 
				
			||||||
 | 
					      cryptoMock,
 | 
				
			||||||
 | 
					      loggerMock,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should be defined', () => {
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -25,12 +25,12 @@ import {
 | 
				
			|||||||
  JobStatus,
 | 
					  JobStatus,
 | 
				
			||||||
  QueueName,
 | 
					  QueueName,
 | 
				
			||||||
} from 'src/interfaces/job.interface';
 | 
					} from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { AudioStreamInfo, IMediaRepository, VideoCodecHWConfig, VideoStreamInfo } from 'src/interfaces/media.interface';
 | 
					import { AudioStreamInfo, IMediaRepository, VideoCodecHWConfig, VideoStreamInfo } from 'src/interfaces/media.interface';
 | 
				
			||||||
import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
					import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
				
			||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
					import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  AV1Config,
 | 
					  AV1Config,
 | 
				
			||||||
  H264Config,
 | 
					  H264Config,
 | 
				
			||||||
@ -46,7 +46,6 @@ import { usePagination } from 'src/utils/pagination';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class MediaService {
 | 
					export class MediaService {
 | 
				
			||||||
  private logger = new ImmichLogger(MediaService.name);
 | 
					 | 
				
			||||||
  private configCore: SystemConfigCore;
 | 
					  private configCore: SystemConfigCore;
 | 
				
			||||||
  private storageCore: StorageCore;
 | 
					  private storageCore: StorageCore;
 | 
				
			||||||
  private hasOpenCL?: boolean = undefined;
 | 
					  private hasOpenCL?: boolean = undefined;
 | 
				
			||||||
@ -60,15 +59,18 @@ export class MediaService {
 | 
				
			|||||||
    @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
 | 
					    @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
 | 
				
			||||||
    @Inject(IMoveRepository) moveRepository: IMoveRepository,
 | 
					    @Inject(IMoveRepository) moveRepository: IMoveRepository,
 | 
				
			||||||
    @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
 | 
					    @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.configCore = SystemConfigCore.create(configRepository);
 | 
					    this.logger.setContext(MediaService.name);
 | 
				
			||||||
 | 
					    this.configCore = SystemConfigCore.create(configRepository, this.logger);
 | 
				
			||||||
    this.storageCore = StorageCore.create(
 | 
					    this.storageCore = StorageCore.create(
 | 
				
			||||||
      assetRepository,
 | 
					      assetRepository,
 | 
				
			||||||
 | 
					      cryptoRepository,
 | 
				
			||||||
      moveRepository,
 | 
					      moveRepository,
 | 
				
			||||||
      personRepository,
 | 
					      personRepository,
 | 
				
			||||||
      cryptoRepository,
 | 
					 | 
				
			||||||
      configRepository,
 | 
					 | 
				
			||||||
      storageRepository,
 | 
					      storageRepository,
 | 
				
			||||||
 | 
					      configRepository,
 | 
				
			||||||
 | 
					      this.logger,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,7 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
				
			|||||||
import { IDatabaseRepository } from 'src/interfaces/database.interface';
 | 
					import { IDatabaseRepository } from 'src/interfaces/database.interface';
 | 
				
			||||||
import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
 | 
					import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
 | 
				
			||||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
 | 
					import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMediaRepository } from 'src/interfaces/media.interface';
 | 
					import { IMediaRepository } from 'src/interfaces/media.interface';
 | 
				
			||||||
import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface';
 | 
					import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface';
 | 
				
			||||||
import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
					import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
				
			||||||
@ -27,6 +28,7 @@ import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.moc
 | 
				
			|||||||
import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
 | 
					import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
 | 
				
			||||||
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
 | 
					import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
 | 
				
			||||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
					import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock';
 | 
					import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock';
 | 
				
			||||||
import { newMetadataRepositoryMock } from 'test/repositories/metadata.repository.mock';
 | 
					import { newMetadataRepositoryMock } from 'test/repositories/metadata.repository.mock';
 | 
				
			||||||
import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock';
 | 
					import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock';
 | 
				
			||||||
@ -48,6 +50,7 @@ describe(MetadataService.name, () => {
 | 
				
			|||||||
  let storageMock: Mocked<IStorageRepository>;
 | 
					  let storageMock: Mocked<IStorageRepository>;
 | 
				
			||||||
  let eventMock: Mocked<IEventRepository>;
 | 
					  let eventMock: Mocked<IEventRepository>;
 | 
				
			||||||
  let databaseMock: Mocked<IDatabaseRepository>;
 | 
					  let databaseMock: Mocked<IDatabaseRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
  let sut: MetadataService;
 | 
					  let sut: MetadataService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
@ -63,6 +66,7 @@ describe(MetadataService.name, () => {
 | 
				
			|||||||
    storageMock = newStorageRepositoryMock();
 | 
					    storageMock = newStorageRepositoryMock();
 | 
				
			||||||
    mediaMock = newMediaRepositoryMock();
 | 
					    mediaMock = newMediaRepositoryMock();
 | 
				
			||||||
    databaseMock = newDatabaseRepositoryMock();
 | 
					    databaseMock = newDatabaseRepositoryMock();
 | 
				
			||||||
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sut = new MetadataService(
 | 
					    sut = new MetadataService(
 | 
				
			||||||
      albumMock,
 | 
					      albumMock,
 | 
				
			||||||
@ -77,6 +81,7 @@ describe(MetadataService.name, () => {
 | 
				
			|||||||
      personMock,
 | 
					      personMock,
 | 
				
			||||||
      storageMock,
 | 
					      storageMock,
 | 
				
			||||||
      configMock,
 | 
					      configMock,
 | 
				
			||||||
 | 
					      loggerMock,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -25,13 +25,13 @@ import {
 | 
				
			|||||||
  JobStatus,
 | 
					  JobStatus,
 | 
				
			||||||
  QueueName,
 | 
					  QueueName,
 | 
				
			||||||
} from 'src/interfaces/job.interface';
 | 
					} from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMediaRepository } from 'src/interfaces/media.interface';
 | 
					import { IMediaRepository } from 'src/interfaces/media.interface';
 | 
				
			||||||
import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface';
 | 
					import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface';
 | 
				
			||||||
import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
					import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
				
			||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
					import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { handlePromiseError } from 'src/utils/misc';
 | 
					import { handlePromiseError } from 'src/utils/misc';
 | 
				
			||||||
import { usePagination } from 'src/utils/pagination';
 | 
					import { usePagination } from 'src/utils/pagination';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -97,7 +97,6 @@ const validate = <T>(value: T): NonNullable<T> | null => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class MetadataService {
 | 
					export class MetadataService {
 | 
				
			||||||
  private logger = new ImmichLogger(MetadataService.name);
 | 
					 | 
				
			||||||
  private storageCore: StorageCore;
 | 
					  private storageCore: StorageCore;
 | 
				
			||||||
  private configCore: SystemConfigCore;
 | 
					  private configCore: SystemConfigCore;
 | 
				
			||||||
  private subscription: Subscription | null = null;
 | 
					  private subscription: Subscription | null = null;
 | 
				
			||||||
@ -115,15 +114,18 @@ export class MetadataService {
 | 
				
			|||||||
    @Inject(IPersonRepository) personRepository: IPersonRepository,
 | 
					    @Inject(IPersonRepository) personRepository: IPersonRepository,
 | 
				
			||||||
    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
					    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
				
			||||||
    @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
 | 
					    @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.configCore = SystemConfigCore.create(configRepository);
 | 
					    this.logger.setContext(MetadataService.name);
 | 
				
			||||||
 | 
					    this.configCore = SystemConfigCore.create(configRepository, this.logger);
 | 
				
			||||||
    this.storageCore = StorageCore.create(
 | 
					    this.storageCore = StorageCore.create(
 | 
				
			||||||
      assetRepository,
 | 
					      assetRepository,
 | 
				
			||||||
 | 
					      cryptoRepository,
 | 
				
			||||||
      moveRepository,
 | 
					      moveRepository,
 | 
				
			||||||
      personRepository,
 | 
					      personRepository,
 | 
				
			||||||
      cryptoRepository,
 | 
					 | 
				
			||||||
      configRepository,
 | 
					 | 
				
			||||||
      storageRepository,
 | 
					      storageRepository,
 | 
				
			||||||
 | 
					      configRepository,
 | 
				
			||||||
 | 
					      this.logger,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import { Colorspace, SystemConfigKey } from 'src/entities/system-config.entity';
 | 
				
			|||||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
 | 
					import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
 | 
				
			||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
					import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
				
			||||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
 | 
					import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
 | 
					import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
 | 
				
			||||||
import { IMediaRepository } from 'src/interfaces/media.interface';
 | 
					import { IMediaRepository } from 'src/interfaces/media.interface';
 | 
				
			||||||
import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
					import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
				
			||||||
@ -23,6 +24,7 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie
 | 
				
			|||||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
					import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
				
			||||||
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
 | 
					import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
 | 
				
			||||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
					import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock';
 | 
					import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock';
 | 
				
			||||||
import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock';
 | 
					import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock';
 | 
				
			||||||
import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock';
 | 
					import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock';
 | 
				
			||||||
@ -72,6 +74,7 @@ describe(PersonService.name, () => {
 | 
				
			|||||||
  let storageMock: Mocked<IStorageRepository>;
 | 
					  let storageMock: Mocked<IStorageRepository>;
 | 
				
			||||||
  let searchMock: Mocked<ISearchRepository>;
 | 
					  let searchMock: Mocked<ISearchRepository>;
 | 
				
			||||||
  let cryptoMock: Mocked<ICryptoRepository>;
 | 
					  let cryptoMock: Mocked<ICryptoRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
  let sut: PersonService;
 | 
					  let sut: PersonService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
@ -86,6 +89,7 @@ describe(PersonService.name, () => {
 | 
				
			|||||||
    storageMock = newStorageRepositoryMock();
 | 
					    storageMock = newStorageRepositoryMock();
 | 
				
			||||||
    searchMock = newSearchRepositoryMock();
 | 
					    searchMock = newSearchRepositoryMock();
 | 
				
			||||||
    cryptoMock = newCryptoRepositoryMock();
 | 
					    cryptoMock = newCryptoRepositoryMock();
 | 
				
			||||||
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
    sut = new PersonService(
 | 
					    sut = new PersonService(
 | 
				
			||||||
      accessMock,
 | 
					      accessMock,
 | 
				
			||||||
      assetMock,
 | 
					      assetMock,
 | 
				
			||||||
@ -98,6 +102,7 @@ describe(PersonService.name, () => {
 | 
				
			|||||||
      jobMock,
 | 
					      jobMock,
 | 
				
			||||||
      searchMock,
 | 
					      searchMock,
 | 
				
			||||||
      cryptoMock,
 | 
					      cryptoMock,
 | 
				
			||||||
 | 
					      loggerMock,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    mediaMock.crop.mockResolvedValue(croppedFace);
 | 
					    mediaMock.crop.mockResolvedValue(croppedFace);
 | 
				
			||||||
 | 
				
			|||||||
@ -38,6 +38,7 @@ import {
 | 
				
			|||||||
  JobStatus,
 | 
					  JobStatus,
 | 
				
			||||||
  QueueName,
 | 
					  QueueName,
 | 
				
			||||||
} from 'src/interfaces/job.interface';
 | 
					} from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
 | 
					import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
 | 
				
			||||||
import { CropOptions, IMediaRepository } from 'src/interfaces/media.interface';
 | 
					import { CropOptions, IMediaRepository } from 'src/interfaces/media.interface';
 | 
				
			||||||
import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
					import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
				
			||||||
@ -46,7 +47,6 @@ import { ISearchRepository } from 'src/interfaces/search.interface';
 | 
				
			|||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { CacheControl, ImmichFileResponse } from 'src/utils/file';
 | 
					import { CacheControl, ImmichFileResponse } from 'src/utils/file';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { mimeTypes } from 'src/utils/mime-types';
 | 
					import { mimeTypes } from 'src/utils/mime-types';
 | 
				
			||||||
import { usePagination } from 'src/utils/pagination';
 | 
					import { usePagination } from 'src/utils/pagination';
 | 
				
			||||||
import { IsNull } from 'typeorm';
 | 
					import { IsNull } from 'typeorm';
 | 
				
			||||||
@ -56,7 +56,6 @@ export class PersonService {
 | 
				
			|||||||
  private access: AccessCore;
 | 
					  private access: AccessCore;
 | 
				
			||||||
  private configCore: SystemConfigCore;
 | 
					  private configCore: SystemConfigCore;
 | 
				
			||||||
  private storageCore: StorageCore;
 | 
					  private storageCore: StorageCore;
 | 
				
			||||||
  readonly logger = new ImmichLogger(PersonService.name);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @Inject(IAccessRepository) accessRepository: IAccessRepository,
 | 
					    @Inject(IAccessRepository) accessRepository: IAccessRepository,
 | 
				
			||||||
@ -70,16 +69,19 @@ export class PersonService {
 | 
				
			|||||||
    @Inject(IJobRepository) private jobRepository: IJobRepository,
 | 
					    @Inject(IJobRepository) private jobRepository: IJobRepository,
 | 
				
			||||||
    @Inject(ISearchRepository) private smartInfoRepository: ISearchRepository,
 | 
					    @Inject(ISearchRepository) private smartInfoRepository: ISearchRepository,
 | 
				
			||||||
    @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
 | 
					    @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.access = AccessCore.create(accessRepository);
 | 
					    this.access = AccessCore.create(accessRepository);
 | 
				
			||||||
    this.configCore = SystemConfigCore.create(configRepository);
 | 
					    this.logger.setContext(PersonService.name);
 | 
				
			||||||
 | 
					    this.configCore = SystemConfigCore.create(configRepository, this.logger);
 | 
				
			||||||
    this.storageCore = StorageCore.create(
 | 
					    this.storageCore = StorageCore.create(
 | 
				
			||||||
      assetRepository,
 | 
					      assetRepository,
 | 
				
			||||||
 | 
					      cryptoRepository,
 | 
				
			||||||
      moveRepository,
 | 
					      moveRepository,
 | 
				
			||||||
      repository,
 | 
					      repository,
 | 
				
			||||||
      cryptoRepository,
 | 
					 | 
				
			||||||
      configRepository,
 | 
					 | 
				
			||||||
      storageRepository,
 | 
					      storageRepository,
 | 
				
			||||||
 | 
					      configRepository,
 | 
				
			||||||
 | 
					      this.logger,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import { mapAsset } from 'src/dtos/asset-response.dto';
 | 
				
			|||||||
import { SearchDto } from 'src/dtos/search.dto';
 | 
					import { SearchDto } from 'src/dtos/search.dto';
 | 
				
			||||||
import { SystemConfigKey } from 'src/entities/system-config.entity';
 | 
					import { SystemConfigKey } from 'src/entities/system-config.entity';
 | 
				
			||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
					import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
 | 
					import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
 | 
				
			||||||
import { IMetadataRepository } from 'src/interfaces/metadata.interface';
 | 
					import { IMetadataRepository } from 'src/interfaces/metadata.interface';
 | 
				
			||||||
import { IPartnerRepository } from 'src/interfaces/partner.interface';
 | 
					import { IPartnerRepository } from 'src/interfaces/partner.interface';
 | 
				
			||||||
@ -13,6 +14,7 @@ import { assetStub } from 'test/fixtures/asset.stub';
 | 
				
			|||||||
import { authStub } from 'test/fixtures/auth.stub';
 | 
					import { authStub } from 'test/fixtures/auth.stub';
 | 
				
			||||||
import { personStub } from 'test/fixtures/person.stub';
 | 
					import { personStub } from 'test/fixtures/person.stub';
 | 
				
			||||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
					import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock';
 | 
					import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock';
 | 
				
			||||||
import { newMetadataRepositoryMock } from 'test/repositories/metadata.repository.mock';
 | 
					import { newMetadataRepositoryMock } from 'test/repositories/metadata.repository.mock';
 | 
				
			||||||
import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock';
 | 
					import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock';
 | 
				
			||||||
@ -32,6 +34,7 @@ describe(SearchService.name, () => {
 | 
				
			|||||||
  let searchMock: Mocked<ISearchRepository>;
 | 
					  let searchMock: Mocked<ISearchRepository>;
 | 
				
			||||||
  let partnerMock: Mocked<IPartnerRepository>;
 | 
					  let partnerMock: Mocked<IPartnerRepository>;
 | 
				
			||||||
  let metadataMock: Mocked<IMetadataRepository>;
 | 
					  let metadataMock: Mocked<IMetadataRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    assetMock = newAssetRepositoryMock();
 | 
					    assetMock = newAssetRepositoryMock();
 | 
				
			||||||
@ -41,8 +44,18 @@ describe(SearchService.name, () => {
 | 
				
			|||||||
    searchMock = newSearchRepositoryMock();
 | 
					    searchMock = newSearchRepositoryMock();
 | 
				
			||||||
    partnerMock = newPartnerRepositoryMock();
 | 
					    partnerMock = newPartnerRepositoryMock();
 | 
				
			||||||
    metadataMock = newMetadataRepositoryMock();
 | 
					    metadataMock = newMetadataRepositoryMock();
 | 
				
			||||||
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sut = new SearchService(configMock, machineMock, personMock, searchMock, assetMock, partnerMock, metadataMock);
 | 
					    sut = new SearchService(
 | 
				
			||||||
 | 
					      configMock,
 | 
				
			||||||
 | 
					      machineMock,
 | 
				
			||||||
 | 
					      personMock,
 | 
				
			||||||
 | 
					      searchMock,
 | 
				
			||||||
 | 
					      assetMock,
 | 
				
			||||||
 | 
					      partnerMock,
 | 
				
			||||||
 | 
					      metadataMock,
 | 
				
			||||||
 | 
					      loggerMock,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should work', () => {
 | 
					  it('should work', () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,7 @@ import {
 | 
				
			|||||||
import { AssetOrder } from 'src/entities/album.entity';
 | 
					import { AssetOrder } from 'src/entities/album.entity';
 | 
				
			||||||
import { AssetEntity } from 'src/entities/asset.entity';
 | 
					import { AssetEntity } from 'src/entities/asset.entity';
 | 
				
			||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
					import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
 | 
					import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
 | 
				
			||||||
import { IMetadataRepository } from 'src/interfaces/metadata.interface';
 | 
					import { IMetadataRepository } from 'src/interfaces/metadata.interface';
 | 
				
			||||||
import { IPartnerRepository } from 'src/interfaces/partner.interface';
 | 
					import { IPartnerRepository } from 'src/interfaces/partner.interface';
 | 
				
			||||||
@ -37,8 +38,10 @@ export class SearchService {
 | 
				
			|||||||
    @Inject(IAssetRepository) private assetRepository: IAssetRepository,
 | 
					    @Inject(IAssetRepository) private assetRepository: IAssetRepository,
 | 
				
			||||||
    @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
 | 
					    @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
 | 
				
			||||||
    @Inject(IMetadataRepository) private metadataRepository: IMetadataRepository,
 | 
					    @Inject(IMetadataRepository) private metadataRepository: IMetadataRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.configCore = SystemConfigCore.create(configRepository);
 | 
					    this.logger.setContext(SearchService.name);
 | 
				
			||||||
 | 
					    this.configCore = SystemConfigCore.create(configRepository, logger);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async searchPerson(auth: AuthDto, dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
 | 
					  async searchPerson(auth: AuthDto, dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import { serverVersion } from 'src/constants';
 | 
					import { serverVersion } from 'src/constants';
 | 
				
			||||||
import { SystemMetadataKey } from 'src/entities/system-metadata.entity';
 | 
					import { SystemMetadataKey } from 'src/entities/system-metadata.entity';
 | 
				
			||||||
import { IEventRepository } from 'src/interfaces/event.interface';
 | 
					import { IEventRepository } from 'src/interfaces/event.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
 | 
					import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
@ -8,6 +9,7 @@ import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interf
 | 
				
			|||||||
import { IUserRepository } from 'src/interfaces/user.interface';
 | 
					import { IUserRepository } from 'src/interfaces/user.interface';
 | 
				
			||||||
import { ServerInfoService } from 'src/services/server-info.service';
 | 
					import { ServerInfoService } from 'src/services/server-info.service';
 | 
				
			||||||
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
 | 
					import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
					import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
				
			||||||
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
					import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
				
			||||||
import { newServerInfoRepositoryMock } from 'test/repositories/system-info.repository.mock';
 | 
					import { newServerInfoRepositoryMock } from 'test/repositories/system-info.repository.mock';
 | 
				
			||||||
@ -23,6 +25,7 @@ describe(ServerInfoService.name, () => {
 | 
				
			|||||||
  let storageMock: Mocked<IStorageRepository>;
 | 
					  let storageMock: Mocked<IStorageRepository>;
 | 
				
			||||||
  let userMock: Mocked<IUserRepository>;
 | 
					  let userMock: Mocked<IUserRepository>;
 | 
				
			||||||
  let systemMetadataMock: Mocked<ISystemMetadataRepository>;
 | 
					  let systemMetadataMock: Mocked<ISystemMetadataRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    configMock = newSystemConfigRepositoryMock();
 | 
					    configMock = newSystemConfigRepositoryMock();
 | 
				
			||||||
@ -31,8 +34,17 @@ describe(ServerInfoService.name, () => {
 | 
				
			|||||||
    storageMock = newStorageRepositoryMock();
 | 
					    storageMock = newStorageRepositoryMock();
 | 
				
			||||||
    userMock = newUserRepositoryMock();
 | 
					    userMock = newUserRepositoryMock();
 | 
				
			||||||
    systemMetadataMock = newSystemMetadataRepositoryMock();
 | 
					    systemMetadataMock = newSystemMetadataRepositoryMock();
 | 
				
			||||||
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sut = new ServerInfoService(eventMock, configMock, userMock, serverInfoMock, storageMock, systemMetadataMock);
 | 
					    sut = new ServerInfoService(
 | 
				
			||||||
 | 
					      eventMock,
 | 
				
			||||||
 | 
					      configMock,
 | 
				
			||||||
 | 
					      userMock,
 | 
				
			||||||
 | 
					      serverInfoMock,
 | 
				
			||||||
 | 
					      storageMock,
 | 
				
			||||||
 | 
					      systemMetadataMock,
 | 
				
			||||||
 | 
					      loggerMock,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should work', () => {
 | 
					  it('should work', () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -15,19 +15,18 @@ import {
 | 
				
			|||||||
} from 'src/dtos/server-info.dto';
 | 
					} from 'src/dtos/server-info.dto';
 | 
				
			||||||
import { SystemMetadataKey } from 'src/entities/system-metadata.entity';
 | 
					import { SystemMetadataKey } from 'src/entities/system-metadata.entity';
 | 
				
			||||||
import { ClientEvent, IEventRepository, ServerEvent, ServerEventMap } from 'src/interfaces/event.interface';
 | 
					import { ClientEvent, IEventRepository, ServerEvent, ServerEventMap } from 'src/interfaces/event.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
 | 
					import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
 | 
					import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
 | 
				
			||||||
import { IUserRepository, UserStatsQueryResponse } from 'src/interfaces/user.interface';
 | 
					import { IUserRepository, UserStatsQueryResponse } from 'src/interfaces/user.interface';
 | 
				
			||||||
import { asHumanReadable } from 'src/utils/bytes';
 | 
					import { asHumanReadable } from 'src/utils/bytes';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { mimeTypes } from 'src/utils/mime-types';
 | 
					import { mimeTypes } from 'src/utils/mime-types';
 | 
				
			||||||
import { Version } from 'src/utils/version';
 | 
					import { Version } from 'src/utils/version';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class ServerInfoService {
 | 
					export class ServerInfoService {
 | 
				
			||||||
  private logger = new ImmichLogger(ServerInfoService.name);
 | 
					 | 
				
			||||||
  private configCore: SystemConfigCore;
 | 
					  private configCore: SystemConfigCore;
 | 
				
			||||||
  private releaseVersion = serverVersion;
 | 
					  private releaseVersion = serverVersion;
 | 
				
			||||||
  private releaseVersionCheckedAt: DateTime | null = null;
 | 
					  private releaseVersionCheckedAt: DateTime | null = null;
 | 
				
			||||||
@ -38,9 +37,11 @@ export class ServerInfoService {
 | 
				
			|||||||
    @Inject(IUserRepository) private userRepository: IUserRepository,
 | 
					    @Inject(IUserRepository) private userRepository: IUserRepository,
 | 
				
			||||||
    @Inject(IServerInfoRepository) private repository: IServerInfoRepository,
 | 
					    @Inject(IServerInfoRepository) private repository: IServerInfoRepository,
 | 
				
			||||||
    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
					    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
				
			||||||
    @Inject(ISystemMetadataRepository) private readonly systemMetadataRepository: ISystemMetadataRepository,
 | 
					    @Inject(ISystemMetadataRepository) private systemMetadataRepository: ISystemMetadataRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.configCore = SystemConfigCore.create(configRepository);
 | 
					    this.logger.setContext(ServerInfoService.name);
 | 
				
			||||||
 | 
					    this.configCore = SystemConfigCore.create(configRepository, this.logger);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onConnect() {}
 | 
					  onConnect() {}
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import { SystemConfigKey } from 'src/entities/system-config.entity';
 | 
				
			|||||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
 | 
					import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
 | 
				
			||||||
import { IDatabaseRepository } from 'src/interfaces/database.interface';
 | 
					import { IDatabaseRepository } from 'src/interfaces/database.interface';
 | 
				
			||||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
 | 
					import { IJobRepository, JobName } from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
 | 
					import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
 | 
				
			||||||
import { ISearchRepository } from 'src/interfaces/search.interface';
 | 
					import { ISearchRepository } from 'src/interfaces/search.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
@ -12,6 +13,7 @@ import { assetStub } from 'test/fixtures/asset.stub';
 | 
				
			|||||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
					import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
				
			||||||
import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
 | 
					import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
 | 
				
			||||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
					import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock';
 | 
					import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock';
 | 
				
			||||||
import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock';
 | 
					import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock';
 | 
				
			||||||
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
					import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
				
			||||||
@ -30,6 +32,7 @@ describe(SmartInfoService.name, () => {
 | 
				
			|||||||
  let searchMock: Mocked<ISearchRepository>;
 | 
					  let searchMock: Mocked<ISearchRepository>;
 | 
				
			||||||
  let machineMock: Mocked<IMachineLearningRepository>;
 | 
					  let machineMock: Mocked<IMachineLearningRepository>;
 | 
				
			||||||
  let databaseMock: Mocked<IDatabaseRepository>;
 | 
					  let databaseMock: Mocked<IDatabaseRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    assetMock = newAssetRepositoryMock();
 | 
					    assetMock = newAssetRepositoryMock();
 | 
				
			||||||
@ -38,7 +41,8 @@ describe(SmartInfoService.name, () => {
 | 
				
			|||||||
    jobMock = newJobRepositoryMock();
 | 
					    jobMock = newJobRepositoryMock();
 | 
				
			||||||
    machineMock = newMachineLearningRepositoryMock();
 | 
					    machineMock = newMachineLearningRepositoryMock();
 | 
				
			||||||
    databaseMock = newDatabaseRepositoryMock();
 | 
					    databaseMock = newDatabaseRepositoryMock();
 | 
				
			||||||
    sut = new SmartInfoService(assetMock, databaseMock, jobMock, machineMock, searchMock, configMock);
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
 | 
					    sut = new SmartInfoService(assetMock, databaseMock, jobMock, machineMock, searchMock, configMock, loggerMock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assetMock.getByIds.mockResolvedValue([asset]);
 | 
					    assetMock.getByIds.mockResolvedValue([asset]);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
				
			|||||||
@ -11,16 +11,15 @@ import {
 | 
				
			|||||||
  JobStatus,
 | 
					  JobStatus,
 | 
				
			||||||
  QueueName,
 | 
					  QueueName,
 | 
				
			||||||
} from 'src/interfaces/job.interface';
 | 
					} from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
 | 
					import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
 | 
				
			||||||
import { ISearchRepository } from 'src/interfaces/search.interface';
 | 
					import { ISearchRepository } from 'src/interfaces/search.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { usePagination } from 'src/utils/pagination';
 | 
					import { usePagination } from 'src/utils/pagination';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class SmartInfoService {
 | 
					export class SmartInfoService {
 | 
				
			||||||
  private configCore: SystemConfigCore;
 | 
					  private configCore: SystemConfigCore;
 | 
				
			||||||
  private logger = new ImmichLogger(SmartInfoService.name);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @Inject(IAssetRepository) private assetRepository: IAssetRepository,
 | 
					    @Inject(IAssetRepository) private assetRepository: IAssetRepository,
 | 
				
			||||||
@ -29,8 +28,10 @@ export class SmartInfoService {
 | 
				
			|||||||
    @Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository,
 | 
					    @Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository,
 | 
				
			||||||
    @Inject(ISearchRepository) private repository: ISearchRepository,
 | 
					    @Inject(ISearchRepository) private repository: ISearchRepository,
 | 
				
			||||||
    @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
 | 
					    @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.configCore = SystemConfigCore.create(configRepository);
 | 
					    this.logger.setContext(SmartInfoService.name);
 | 
				
			||||||
 | 
					    this.configCore = SystemConfigCore.create(configRepository, this.logger);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async init() {
 | 
					  async init() {
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,7 @@ import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
				
			|||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
					import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
				
			||||||
import { IDatabaseRepository } from 'src/interfaces/database.interface';
 | 
					import { IDatabaseRepository } from 'src/interfaces/database.interface';
 | 
				
			||||||
import { JobStatus } from 'src/interfaces/job.interface';
 | 
					import { JobStatus } from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
					import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
				
			||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
					import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
@ -20,6 +21,7 @@ import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock'
 | 
				
			|||||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
					import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
				
			||||||
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
 | 
					import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
 | 
				
			||||||
import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
 | 
					import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock';
 | 
					import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock';
 | 
				
			||||||
import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
 | 
					import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
 | 
				
			||||||
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
					import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
				
			||||||
@ -38,6 +40,7 @@ describe(StorageTemplateService.name, () => {
 | 
				
			|||||||
  let userMock: Mocked<IUserRepository>;
 | 
					  let userMock: Mocked<IUserRepository>;
 | 
				
			||||||
  let cryptoMock: Mocked<ICryptoRepository>;
 | 
					  let cryptoMock: Mocked<ICryptoRepository>;
 | 
				
			||||||
  let databaseMock: Mocked<IDatabaseRepository>;
 | 
					  let databaseMock: Mocked<IDatabaseRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should work', () => {
 | 
					  it('should work', () => {
 | 
				
			||||||
    expect(sut).toBeDefined();
 | 
					    expect(sut).toBeDefined();
 | 
				
			||||||
@ -53,6 +56,7 @@ describe(StorageTemplateService.name, () => {
 | 
				
			|||||||
    userMock = newUserRepositoryMock();
 | 
					    userMock = newUserRepositoryMock();
 | 
				
			||||||
    cryptoMock = newCryptoRepositoryMock();
 | 
					    cryptoMock = newCryptoRepositoryMock();
 | 
				
			||||||
    databaseMock = newDatabaseRepositoryMock();
 | 
					    databaseMock = newDatabaseRepositoryMock();
 | 
				
			||||||
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    configMock.load.mockResolvedValue([{ key: SystemConfigKey.STORAGE_TEMPLATE_ENABLED, value: true }]);
 | 
					    configMock.load.mockResolvedValue([{ key: SystemConfigKey.STORAGE_TEMPLATE_ENABLED, value: true }]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -66,9 +70,10 @@ describe(StorageTemplateService.name, () => {
 | 
				
			|||||||
      userMock,
 | 
					      userMock,
 | 
				
			||||||
      cryptoMock,
 | 
					      cryptoMock,
 | 
				
			||||||
      databaseMock,
 | 
					      databaseMock,
 | 
				
			||||||
 | 
					      loggerMock,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SystemConfigCore.create(configMock).config$.next(defaults);
 | 
					    SystemConfigCore.create(configMock, loggerMock).config$.next(defaults);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('onValidateConfig', () => {
 | 
					  describe('onValidateConfig', () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -24,13 +24,13 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
				
			|||||||
import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface';
 | 
					import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface';
 | 
				
			||||||
import { ServerAsyncEvent, ServerAsyncEventMap } from 'src/interfaces/event.interface';
 | 
					import { ServerAsyncEvent, ServerAsyncEventMap } from 'src/interfaces/event.interface';
 | 
				
			||||||
import { IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.interface';
 | 
					import { IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
					import { IMoveRepository } from 'src/interfaces/move.interface';
 | 
				
			||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
					import { IPersonRepository } from 'src/interfaces/person.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { IUserRepository } from 'src/interfaces/user.interface';
 | 
					import { IUserRepository } from 'src/interfaces/user.interface';
 | 
				
			||||||
import { getLivePhotoMotionFilename } from 'src/utils/file';
 | 
					import { getLivePhotoMotionFilename } from 'src/utils/file';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { usePagination } from 'src/utils/pagination';
 | 
					import { usePagination } from 'src/utils/pagination';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface MoveAssetMetadata {
 | 
					export interface MoveAssetMetadata {
 | 
				
			||||||
@ -47,7 +47,6 @@ interface RenderMetadata {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class StorageTemplateService {
 | 
					export class StorageTemplateService {
 | 
				
			||||||
  private logger = new ImmichLogger(StorageTemplateService.name);
 | 
					 | 
				
			||||||
  private configCore: SystemConfigCore;
 | 
					  private configCore: SystemConfigCore;
 | 
				
			||||||
  private storageCore: StorageCore;
 | 
					  private storageCore: StorageCore;
 | 
				
			||||||
  private _template: {
 | 
					  private _template: {
 | 
				
			||||||
@ -73,16 +72,19 @@ export class StorageTemplateService {
 | 
				
			|||||||
    @Inject(IUserRepository) private userRepository: IUserRepository,
 | 
					    @Inject(IUserRepository) private userRepository: IUserRepository,
 | 
				
			||||||
    @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
 | 
					    @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
 | 
				
			||||||
    @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository,
 | 
					    @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.configCore = SystemConfigCore.create(configRepository);
 | 
					    this.logger.setContext(StorageTemplateService.name);
 | 
				
			||||||
 | 
					    this.configCore = SystemConfigCore.create(configRepository, this.logger);
 | 
				
			||||||
    this.configCore.config$.subscribe((config) => this.onConfig(config));
 | 
					    this.configCore.config$.subscribe((config) => this.onConfig(config));
 | 
				
			||||||
    this.storageCore = StorageCore.create(
 | 
					    this.storageCore = StorageCore.create(
 | 
				
			||||||
      assetRepository,
 | 
					      assetRepository,
 | 
				
			||||||
 | 
					      cryptoRepository,
 | 
				
			||||||
      moveRepository,
 | 
					      moveRepository,
 | 
				
			||||||
      personRepository,
 | 
					      personRepository,
 | 
				
			||||||
      cryptoRepository,
 | 
					 | 
				
			||||||
      configRepository,
 | 
					 | 
				
			||||||
      storageRepository,
 | 
					      storageRepository,
 | 
				
			||||||
 | 
					      configRepository,
 | 
				
			||||||
 | 
					      this.logger,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,15 +1,19 @@
 | 
				
			|||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { StorageService } from 'src/services/storage.service';
 | 
					import { StorageService } from 'src/services/storage.service';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
					import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
				
			||||||
import { Mocked } from 'vitest';
 | 
					import { Mocked } from 'vitest';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe(StorageService.name, () => {
 | 
					describe(StorageService.name, () => {
 | 
				
			||||||
  let sut: StorageService;
 | 
					  let sut: StorageService;
 | 
				
			||||||
  let storageMock: Mocked<IStorageRepository>;
 | 
					  let storageMock: Mocked<IStorageRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    storageMock = newStorageRepositoryMock();
 | 
					    storageMock = newStorageRepositoryMock();
 | 
				
			||||||
    sut = new StorageService(storageMock);
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
 | 
					    sut = new StorageService(storageMock, loggerMock);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should work', () => {
 | 
					  it('should work', () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,14 +1,17 @@
 | 
				
			|||||||
import { Inject, Injectable } from '@nestjs/common';
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
 | 
					import { StorageCore, StorageFolder } from 'src/cores/storage.core';
 | 
				
			||||||
import { IDeleteFilesJob, JobStatus } from 'src/interfaces/job.interface';
 | 
					import { IDeleteFilesJob, JobStatus } from 'src/interfaces/job.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class StorageService {
 | 
					export class StorageService {
 | 
				
			||||||
  private logger = new ImmichLogger(StorageService.name);
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
				
			||||||
  constructor(@Inject(IStorageRepository) private storageRepository: IStorageRepository) {}
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    this.logger.setContext(StorageService.name);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  init() {
 | 
					  init() {
 | 
				
			||||||
    const libraryBase = StorageCore.getBaseFolder(StorageFolder.LIBRARY);
 | 
					    const libraryBase = StorageCore.getBaseFolder(StorageFolder.LIBRARY);
 | 
				
			||||||
 | 
				
			|||||||
@ -20,11 +20,10 @@ import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			|||||||
import { ISearchRepository } from 'src/interfaces/search.interface';
 | 
					import { ISearchRepository } from 'src/interfaces/search.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { SystemConfigService } from 'src/services/system-config.service';
 | 
					import { SystemConfigService } from 'src/services/system-config.service';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
 | 
					import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
 | 
				
			||||||
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
					import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
				
			||||||
import { MockInstance, Mocked, vitest } from 'vitest';
 | 
					import { Mocked } from 'vitest';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const updates: SystemConfigEntity[] = [
 | 
					const updates: SystemConfigEntity[] = [
 | 
				
			||||||
  { key: SystemConfigKey.FFMPEG_CRF, value: 30 },
 | 
					  { key: SystemConfigKey.FFMPEG_CRF, value: 30 },
 | 
				
			||||||
@ -184,16 +183,6 @@ describe(SystemConfigService.name, () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('getConfig', () => {
 | 
					  describe('getConfig', () => {
 | 
				
			||||||
    let warnLog: MockInstance;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    beforeEach(() => {
 | 
					 | 
				
			||||||
      warnLog = vitest.spyOn(ImmichLogger.prototype, 'warn');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    afterEach(() => {
 | 
					 | 
				
			||||||
      warnLog.mockRestore();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should return the default config', async () => {
 | 
					    it('should return the default config', async () => {
 | 
				
			||||||
      configMock.load.mockResolvedValue([]);
 | 
					      configMock.load.mockResolvedValue([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -271,7 +260,7 @@ describe(SystemConfigService.name, () => {
 | 
				
			|||||||
      configMock.readFile.mockResolvedValue(partialConfig);
 | 
					      configMock.readFile.mockResolvedValue(partialConfig);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await sut.getConfig();
 | 
					      await sut.getConfig();
 | 
				
			||||||
      expect(warnLog).toHaveBeenCalled();
 | 
					      expect(loggerMock.warn).toHaveBeenCalled();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const tests = [
 | 
					    const tests = [
 | 
				
			||||||
@ -290,7 +279,7 @@ describe(SystemConfigService.name, () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (test.warn) {
 | 
					        if (test.warn) {
 | 
				
			||||||
          await sut.getConfig();
 | 
					          await sut.getConfig();
 | 
				
			||||||
          expect(warnLog).toHaveBeenCalled();
 | 
					          expect(loggerMock.warn).toHaveBeenCalled();
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          await expect(sut.getConfig()).rejects.toBeInstanceOf(Error);
 | 
					          await expect(sut.getConfig()).rejects.toBeInstanceOf(Error);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -36,9 +36,9 @@ export class SystemConfigService {
 | 
				
			|||||||
    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
    @Inject(ISearchRepository) private smartInfoRepository: ISearchRepository,
 | 
					    @Inject(ISearchRepository) private smartInfoRepository: ISearchRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.core = SystemConfigCore.create(repository);
 | 
					 | 
				
			||||||
    this.core.config$.subscribe((config) => this.setLogLevel(config));
 | 
					 | 
				
			||||||
    this.logger.setContext(SystemConfigService.name);
 | 
					    this.logger.setContext(SystemConfigService.name);
 | 
				
			||||||
 | 
					    this.core = SystemConfigCore.create(repository, this.logger);
 | 
				
			||||||
 | 
					    this.core.config$.subscribe((config) => this.setLogLevel(config));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async init() {
 | 
					  async init() {
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,7 @@ import { IAlbumRepository } from 'src/interfaces/album.interface';
 | 
				
			|||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
					import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
				
			||||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
 | 
					import { IJobRepository, JobName } from 'src/interfaces/job.interface';
 | 
				
			||||||
import { ILibraryRepository } from 'src/interfaces/library.interface';
 | 
					import { ILibraryRepository } from 'src/interfaces/library.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { IUserRepository } from 'src/interfaces/user.interface';
 | 
					import { IUserRepository } from 'src/interfaces/user.interface';
 | 
				
			||||||
@ -22,6 +23,7 @@ import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock'
 | 
				
			|||||||
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
 | 
					import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
 | 
				
			||||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
					import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
				
			||||||
import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock';
 | 
					import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock';
 | 
				
			||||||
 | 
					import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
				
			||||||
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
					import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
 | 
				
			||||||
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
					import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
 | 
				
			||||||
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
 | 
					import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
 | 
				
			||||||
@ -43,6 +45,7 @@ describe(UserService.name, () => {
 | 
				
			|||||||
  let libraryMock: Mocked<ILibraryRepository>;
 | 
					  let libraryMock: Mocked<ILibraryRepository>;
 | 
				
			||||||
  let storageMock: Mocked<IStorageRepository>;
 | 
					  let storageMock: Mocked<IStorageRepository>;
 | 
				
			||||||
  let configMock: Mocked<ISystemConfigRepository>;
 | 
					  let configMock: Mocked<ISystemConfigRepository>;
 | 
				
			||||||
 | 
					  let loggerMock: Mocked<ILoggerRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    albumMock = newAlbumRepositoryMock();
 | 
					    albumMock = newAlbumRepositoryMock();
 | 
				
			||||||
@ -52,8 +55,18 @@ describe(UserService.name, () => {
 | 
				
			|||||||
    libraryMock = newLibraryRepositoryMock();
 | 
					    libraryMock = newLibraryRepositoryMock();
 | 
				
			||||||
    storageMock = newStorageRepositoryMock();
 | 
					    storageMock = newStorageRepositoryMock();
 | 
				
			||||||
    userMock = newUserRepositoryMock();
 | 
					    userMock = newUserRepositoryMock();
 | 
				
			||||||
 | 
					    loggerMock = newLoggerRepositoryMock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sut = new UserService(albumMock, cryptoRepositoryMock, jobMock, libraryMock, storageMock, configMock, userMock);
 | 
					    sut = new UserService(
 | 
				
			||||||
 | 
					      albumMock,
 | 
				
			||||||
 | 
					      cryptoRepositoryMock,
 | 
				
			||||||
 | 
					      jobMock,
 | 
				
			||||||
 | 
					      libraryMock,
 | 
				
			||||||
 | 
					      storageMock,
 | 
				
			||||||
 | 
					      configMock,
 | 
				
			||||||
 | 
					      userMock,
 | 
				
			||||||
 | 
					      loggerMock,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    userMock.get.mockImplementation((userId) =>
 | 
					    userMock.get.mockImplementation((userId) =>
 | 
				
			||||||
      Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? null),
 | 
					      Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? null),
 | 
				
			||||||
 | 
				
			|||||||
@ -11,16 +11,15 @@ import { IAlbumRepository } from 'src/interfaces/album.interface';
 | 
				
			|||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
					import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
				
			||||||
import { IEntityJob, IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
 | 
					import { IEntityJob, IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
 | 
				
			||||||
import { ILibraryRepository } from 'src/interfaces/library.interface';
 | 
					import { ILibraryRepository } from 'src/interfaces/library.interface';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
					import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
				
			||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
					import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
 | 
				
			||||||
import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface';
 | 
					import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface';
 | 
				
			||||||
import { CacheControl, ImmichFileResponse } from 'src/utils/file';
 | 
					import { CacheControl, ImmichFileResponse } from 'src/utils/file';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class UserService {
 | 
					export class UserService {
 | 
				
			||||||
  private configCore: SystemConfigCore;
 | 
					  private configCore: SystemConfigCore;
 | 
				
			||||||
  private logger = new ImmichLogger(UserService.name);
 | 
					 | 
				
			||||||
  private userCore: UserCore;
 | 
					  private userCore: UserCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
@ -31,9 +30,11 @@ export class UserService {
 | 
				
			|||||||
    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
					    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
				
			||||||
    @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
 | 
					    @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
 | 
				
			||||||
    @Inject(IUserRepository) private userRepository: IUserRepository,
 | 
					    @Inject(IUserRepository) private userRepository: IUserRepository,
 | 
				
			||||||
 | 
					    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.userCore = UserCore.create(cryptoRepository, libraryRepository, userRepository);
 | 
					    this.userCore = UserCore.create(cryptoRepository, libraryRepository, userRepository);
 | 
				
			||||||
    this.configCore = SystemConfigCore.create(configRepository);
 | 
					    this.logger.setContext(UserService.name);
 | 
				
			||||||
 | 
					    this.configCore = SystemConfigCore.create(configRepository, this.logger);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async getAll(auth: AuthDto, isAll: boolean): Promise<UserResponseDto[]> {
 | 
					  async getAll(auth: AuthDto, isAll: boolean): Promise<UserResponseDto[]> {
 | 
				
			||||||
 | 
				
			|||||||
@ -17,12 +17,12 @@ import {
 | 
				
			|||||||
  IMMICH_API_KEY_NAME,
 | 
					  IMMICH_API_KEY_NAME,
 | 
				
			||||||
  serverVersion,
 | 
					  serverVersion,
 | 
				
			||||||
} from 'src/constants';
 | 
					} from 'src/constants';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { Metadata } from 'src/middleware/auth.guard';
 | 
					import { Metadata } from 'src/middleware/auth.guard';
 | 
				
			||||||
import { ImmichLogger } from 'src/utils/logger';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const isConnectionAborted = (error: Error | any) => error.code === 'ECONNABORTED';
 | 
					export const isConnectionAborted = (error: Error | any) => error.code === 'ECONNABORTED';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const handlePromiseError = <T>(promise: Promise<T>, logger: ImmichLogger): void => {
 | 
					export const handlePromiseError = <T>(promise: Promise<T>, logger: ILoggerRepository): void => {
 | 
				
			||||||
  promise.catch((error: Error | any) => logger.error(`Promise error: ${error}`, error?.stack));
 | 
					  promise.catch((error: Error | any) => logger.error(`Promise error: ${error}`, error?.stack));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ import { format } from 'sql-formatter';
 | 
				
			|||||||
import { databaseConfig } from 'src/database.config';
 | 
					import { databaseConfig } from 'src/database.config';
 | 
				
			||||||
import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators';
 | 
					import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators';
 | 
				
			||||||
import { entities } from 'src/entities';
 | 
					import { entities } from 'src/entities';
 | 
				
			||||||
 | 
					import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
				
			||||||
import { repositories } from 'src/repositories';
 | 
					import { repositories } from 'src/repositories';
 | 
				
			||||||
import { AccessRepository } from 'src/repositories/access.repository';
 | 
					import { AccessRepository } from 'src/repositories/access.repository';
 | 
				
			||||||
import { AuthService } from 'src/services/auth.service';
 | 
					import { AuthService } from 'src/services/auth.service';
 | 
				
			||||||
@ -58,6 +59,9 @@ class SqlGenerator {
 | 
				
			|||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await this.setup();
 | 
					      await this.setup();
 | 
				
			||||||
      for (const repository of repositories) {
 | 
					      for (const repository of repositories) {
 | 
				
			||||||
 | 
					        if (repository.provide === ILoggerRepository) {
 | 
				
			||||||
 | 
					          continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        await this.process(repository);
 | 
					        await this.process(repository);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      await this.write();
 | 
					      await this.write();
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user