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:
AmAn Sharma 2024-04-17 03:00:31 +05:30 committed by GitHub
parent 877207a2e6
commit 6e6deec40c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 352 additions and 191 deletions

View File

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

View File

@ -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[]> {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

@ -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', () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

@ -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', () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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', () => {

View File

@ -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[]> {

View File

@ -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', () => {

View File

@ -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() {}

View File

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

View File

@ -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() {

View File

@ -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', () => {

View File

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

View File

@ -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', () => {

View File

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

View File

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

View File

@ -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() {

View File

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

View File

@ -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[]> {

View File

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

View File

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