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