mirror of
https://github.com/immich-app/immich.git
synced 2024-12-29 15:11:58 +00:00
parent
16f2364e93
commit
e6a666f1d3
45 changed files with 143 additions and 202 deletions
|
@ -14,6 +14,7 @@ import { entities } from 'src/entities';
|
||||||
import { ImmichWorker } from 'src/enum';
|
import { ImmichWorker } from 'src/enum';
|
||||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
|
import { ITelemetryRepository } from 'src/interfaces/telemetry.interface';
|
||||||
import { AuthGuard } from 'src/middleware/auth.guard';
|
import { AuthGuard } from 'src/middleware/auth.guard';
|
||||||
import { ErrorInterceptor } from 'src/middleware/error.interceptor';
|
import { ErrorInterceptor } from 'src/middleware/error.interceptor';
|
||||||
import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor';
|
import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor';
|
||||||
|
@ -21,6 +22,7 @@ import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter';
|
||||||
import { LoggingInterceptor } from 'src/middleware/logging.interceptor';
|
import { LoggingInterceptor } from 'src/middleware/logging.interceptor';
|
||||||
import { repositories } from 'src/repositories';
|
import { repositories } from 'src/repositories';
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
|
import { teardownTelemetry } from 'src/repositories/telemetry.repository';
|
||||||
import { services } from 'src/services';
|
import { services } from 'src/services';
|
||||||
import { DatabaseService } from 'src/services/database.service';
|
import { DatabaseService } from 'src/services/database.service';
|
||||||
|
|
||||||
|
@ -66,6 +68,7 @@ abstract class BaseModule implements OnModuleInit, OnModuleDestroy {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
||||||
|
@Inject(ITelemetryRepository) private telemetryRepository: ITelemetryRepository,
|
||||||
) {
|
) {
|
||||||
logger.setAppName(this.worker);
|
logger.setAppName(this.worker);
|
||||||
}
|
}
|
||||||
|
@ -73,12 +76,14 @@ abstract class BaseModule implements OnModuleInit, OnModuleDestroy {
|
||||||
abstract getWorker(): ImmichWorker;
|
abstract getWorker(): ImmichWorker;
|
||||||
|
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
|
this.telemetryRepository.setup({ repositories: repositories.map(({ useClass }) => useClass) });
|
||||||
this.eventRepository.setup({ services });
|
this.eventRepository.setup({ services });
|
||||||
await this.eventRepository.emit('app.bootstrap', this.worker);
|
await this.eventRepository.emit('app.bootstrap', this.worker);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onModuleDestroy() {
|
async onModuleDestroy() {
|
||||||
await this.eventRepository.emit('app.shutdown', this.worker);
|
await this.eventRepository.emit('app.shutdown', this.worker);
|
||||||
|
await teardownTelemetry();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -354,9 +354,9 @@ export const immichAppConfig: ConfigModuleOptions = {
|
||||||
),
|
),
|
||||||
|
|
||||||
IMMICH_METRICS: Joi.boolean().optional().default(false),
|
IMMICH_METRICS: Joi.boolean().optional().default(false),
|
||||||
IMMICH_HOST_METRICS: Joi.boolean().optional().default(false),
|
IMMICH_HOST_METRICS: Joi.boolean().optional(),
|
||||||
IMMICH_API_METRICS: Joi.boolean().optional().default(false),
|
IMMICH_API_METRICS: Joi.boolean().optional(),
|
||||||
IMMICH_IO_METRICS: Joi.boolean().optional().default(false),
|
IMMICH_IO_METRICS: Joi.boolean().optional(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -86,27 +86,6 @@ export function ChunkedSet(options?: { paramIndex?: number }): MethodDecorator {
|
||||||
return Chunked({ ...options, mergeFn: setUnion });
|
return Chunked({ ...options, mergeFn: setUnion });
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/74898678
|
|
||||||
export function DecorateAll(
|
|
||||||
decorator: <T>(
|
|
||||||
target: any,
|
|
||||||
propertyKey: string,
|
|
||||||
descriptor: TypedPropertyDescriptor<T>,
|
|
||||||
) => TypedPropertyDescriptor<T> | void,
|
|
||||||
) {
|
|
||||||
return (target: any) => {
|
|
||||||
const descriptors = Object.getOwnPropertyDescriptors(target.prototype);
|
|
||||||
for (const [propName, descriptor] of Object.entries(descriptors)) {
|
|
||||||
const isMethod = typeof descriptor.value == 'function' && propName !== 'constructor';
|
|
||||||
if (!isMethod) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
decorator({ ...target, constructor: { ...target.constructor, name: target.name } as any }, propName, descriptor);
|
|
||||||
Object.defineProperty(target.prototype, propName, descriptor);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const UUID = '00000000-0000-4000-a000-000000000000';
|
const UUID = '00000000-0000-4000-a000-000000000000';
|
||||||
|
|
||||||
export const DummyValue = {
|
export const DummyValue = {
|
||||||
|
@ -128,6 +107,9 @@ export interface GenerateSqlQueries {
|
||||||
params: unknown[];
|
params: unknown[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Telemetry = (options: { enabled?: boolean }) =>
|
||||||
|
SetMetadata(MetadataKey.TELEMETRY_ENABLED, options?.enabled ?? true);
|
||||||
|
|
||||||
/** Decorator to enable versioning/tracking of generated Sql */
|
/** Decorator to enable versioning/tracking of generated Sql */
|
||||||
export const GenerateSql = (...options: GenerateSqlQueries[]) => SetMetadata(GENERATE_SQL_KEY, options);
|
export const GenerateSql = (...options: GenerateSqlQueries[]) => SetMetadata(GENERATE_SQL_KEY, options);
|
||||||
|
|
||||||
|
|
|
@ -334,6 +334,7 @@ export enum MetadataKey {
|
||||||
SHARED_ROUTE = 'shared_route',
|
SHARED_ROUTE = 'shared_route',
|
||||||
API_KEY_SECURITY = 'api_key',
|
API_KEY_SECURITY = 'api_key',
|
||||||
EVENT_CONFIG = 'event_config',
|
EVENT_CONFIG = 'event_config',
|
||||||
|
TELEMETRY_ENABLED = 'telemetry_enabled',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum RouteKey {
|
export enum RouteKey {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { MetricOptions } from '@opentelemetry/api';
|
import { MetricOptions } from '@opentelemetry/api';
|
||||||
|
import { ClassConstructor } from 'class-transformer';
|
||||||
|
|
||||||
export const ITelemetryRepository = 'ITelemetryRepository';
|
export const ITelemetryRepository = 'ITelemetryRepository';
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ export interface IMetricGroupRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITelemetryRepository {
|
export interface ITelemetryRepository {
|
||||||
|
setup(options: { repositories: ClassConstructor<unknown>[] }): void;
|
||||||
api: IMetricGroupRepository;
|
api: IMetricGroupRepository;
|
||||||
host: IMetricGroupRepository;
|
host: IMetricGroupRepository;
|
||||||
jobs: IMetricGroupRepository;
|
jobs: IMetricGroupRepository;
|
||||||
|
|
|
@ -15,7 +15,6 @@ import { StackEntity } from 'src/entities/stack.entity';
|
||||||
import { TagEntity } from 'src/entities/tag.entity';
|
import { TagEntity } from 'src/entities/tag.entity';
|
||||||
import { AlbumUserRole } from 'src/enum';
|
import { AlbumUserRole } from 'src/enum';
|
||||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { Brackets, In, Repository } from 'typeorm';
|
import { Brackets, In, Repository } from 'typeorm';
|
||||||
|
|
||||||
type IActivityAccess = IAccessRepository['activity'];
|
type IActivityAccess = IAccessRepository['activity'];
|
||||||
|
@ -29,7 +28,6 @@ type IStackAccess = IAccessRepository['stack'];
|
||||||
type ITagAccess = IAccessRepository['tag'];
|
type ITagAccess = IAccessRepository['tag'];
|
||||||
type ITimelineAccess = IAccessRepository['timeline'];
|
type ITimelineAccess = IAccessRepository['timeline'];
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class ActivityAccess implements IActivityAccess {
|
class ActivityAccess implements IActivityAccess {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { ActivityEntity } from 'src/entities/activity.entity';
|
import { ActivityEntity } from 'src/entities/activity.entity';
|
||||||
import { IActivityRepository } from 'src/interfaces/activity.interface';
|
import { IActivityRepository } from 'src/interfaces/activity.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { IsNull, Repository } from 'typeorm';
|
import { IsNull, Repository } from 'typeorm';
|
||||||
|
|
||||||
export interface ActivitySearch {
|
export interface ActivitySearch {
|
||||||
|
@ -13,7 +12,6 @@ export interface ActivitySearch {
|
||||||
isLiked?: boolean;
|
isLiked?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ActivityRepository implements IActivityRepository {
|
export class ActivityRepository implements IActivityRepository {
|
||||||
constructor(@InjectRepository(ActivityEntity) private repository: Repository<ActivityEntity>) {}
|
constructor(@InjectRepository(ActivityEntity) private repository: Repository<ActivityEntity>) {}
|
||||||
|
|
|
@ -2,10 +2,8 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { AlbumUserEntity } from 'src/entities/album-user.entity';
|
import { AlbumUserEntity } from 'src/entities/album-user.entity';
|
||||||
import { AlbumPermissionId, IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
import { AlbumPermissionId, IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AlbumUserRepository implements IAlbumUserRepository {
|
export class AlbumUserRepository implements IAlbumUserRepository {
|
||||||
constructor(@InjectRepository(AlbumUserEntity) private repository: Repository<AlbumUserEntity>) {}
|
constructor(@InjectRepository(AlbumUserEntity) private repository: Repository<AlbumUserEntity>) {}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/
|
||||||
import { AlbumEntity } from 'src/entities/album.entity';
|
import { AlbumEntity } from 'src/entities/album.entity';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface';
|
import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import {
|
import {
|
||||||
DataSource,
|
DataSource,
|
||||||
EntityManager,
|
EntityManager,
|
||||||
|
@ -23,7 +22,6 @@ const withoutDeletedUsers = <T extends AlbumEntity | null>(album: T) => {
|
||||||
return album;
|
return album;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AlbumRepository implements IAlbumRepository {
|
export class AlbumRepository implements IAlbumRepository {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -3,10 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { APIKeyEntity } from 'src/entities/api-key.entity';
|
import { APIKeyEntity } from 'src/entities/api-key.entity';
|
||||||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApiKeyRepository implements IKeyRepository {
|
export class ApiKeyRepository implements IKeyRepository {
|
||||||
constructor(@InjectRepository(APIKeyEntity) private repository: Repository<APIKeyEntity>) {}
|
constructor(@InjectRepository(APIKeyEntity) private repository: Repository<APIKeyEntity>) {}
|
||||||
|
|
|
@ -29,7 +29,6 @@ import {
|
||||||
} from 'src/interfaces/asset.interface';
|
} from 'src/interfaces/asset.interface';
|
||||||
import { AssetSearchOptions, SearchExploreItem } from 'src/interfaces/search.interface';
|
import { AssetSearchOptions, SearchExploreItem } from 'src/interfaces/search.interface';
|
||||||
import { searchAssetBuilder } from 'src/utils/database';
|
import { searchAssetBuilder } from 'src/utils/database';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { Paginated, PaginationOptions, paginate, paginatedBuilder } from 'src/utils/pagination';
|
import { Paginated, PaginationOptions, paginate, paginatedBuilder } from 'src/utils/pagination';
|
||||||
import {
|
import {
|
||||||
Brackets,
|
Brackets,
|
||||||
|
@ -54,7 +53,6 @@ const dateTrunc = (options: TimeBucketOptions) =>
|
||||||
truncateMap[options.size]
|
truncateMap[options.size]
|
||||||
}', (asset."localDateTime" at time zone 'UTC')) at time zone 'UTC')::timestamptz`;
|
}', (asset."localDateTime" at time zone 'UTC')) at time zone 'UTC')::timestamptz`;
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AssetRepository implements IAssetRepository {
|
export class AssetRepository implements IAssetRepository {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -2,10 +2,8 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { AuditEntity } from 'src/entities/audit.entity';
|
import { AuditEntity } from 'src/entities/audit.entity';
|
||||||
import { AuditSearch, IAuditRepository } from 'src/interfaces/audit.interface';
|
import { AuditSearch, IAuditRepository } from 'src/interfaces/audit.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { In, LessThan, MoreThan, Repository } from 'typeorm';
|
import { In, LessThan, MoreThan, Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuditRepository implements IAuditRepository {
|
export class AuditRepository implements IAuditRepository {
|
||||||
constructor(@InjectRepository(AuditEntity) private repository: Repository<AuditEntity>) {}
|
constructor(@InjectRepository(AuditEntity) private repository: Repository<AuditEntity>) {}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { citiesFile, excludePaths } from 'src/constants';
|
import { citiesFile, excludePaths } from 'src/constants';
|
||||||
|
import { Telemetry } from 'src/decorators';
|
||||||
import { ImmichEnvironment, ImmichWorker, LogLevel } from 'src/enum';
|
import { ImmichEnvironment, ImmichWorker, LogLevel } from 'src/enum';
|
||||||
import { EnvData, IConfigRepository } from 'src/interfaces/config.interface';
|
import { EnvData, IConfigRepository } from 'src/interfaces/config.interface';
|
||||||
import { DatabaseExtension } from 'src/interfaces/database.interface';
|
import { DatabaseExtension } from 'src/interfaces/database.interface';
|
||||||
|
@ -74,9 +75,6 @@ const getEnv = (): EnvData => {
|
||||||
const repoMetrics = parseBoolean(process.env.IMMICH_IO_METRICS, globalEnabled);
|
const repoMetrics = parseBoolean(process.env.IMMICH_IO_METRICS, globalEnabled);
|
||||||
const jobMetrics = parseBoolean(process.env.IMMICH_JOB_METRICS, globalEnabled);
|
const jobMetrics = parseBoolean(process.env.IMMICH_JOB_METRICS, globalEnabled);
|
||||||
const telemetryEnabled = globalEnabled || hostMetrics || apiMetrics || repoMetrics || jobMetrics;
|
const telemetryEnabled = globalEnabled || hostMetrics || apiMetrics || repoMetrics || jobMetrics;
|
||||||
if (!telemetryEnabled && process.env.OTEL_SDK_DISABLED === undefined) {
|
|
||||||
process.env.OTEL_SDK_DISABLED = 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
host: process.env.IMMICH_HOST,
|
host: process.env.IMMICH_HOST,
|
||||||
|
@ -186,6 +184,7 @@ const getEnv = (): EnvData => {
|
||||||
let cached: EnvData | undefined;
|
let cached: EnvData | undefined;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@Telemetry({ enabled: false })
|
||||||
export class ConfigRepository implements IConfigRepository {
|
export class ConfigRepository implements IConfigRepository {
|
||||||
getEnv(): EnvData {
|
getEnv(): EnvData {
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
|
|
|
@ -3,9 +3,7 @@ import { compareSync, hash } from 'bcrypt';
|
||||||
import { createHash, createPublicKey, createVerify, randomBytes, randomUUID } from 'node:crypto';
|
import { createHash, createPublicKey, createVerify, randomBytes, randomUUID } from 'node:crypto';
|
||||||
import { createReadStream } from 'node:fs';
|
import { createReadStream } from 'node:fs';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CryptoRepository implements ICryptoRepository {
|
export class CryptoRepository implements ICryptoRepository {
|
||||||
randomUUID() {
|
randomUUID() {
|
||||||
|
|
|
@ -15,11 +15,9 @@ import {
|
||||||
VectorUpdateResult,
|
VectorUpdateResult,
|
||||||
} from 'src/interfaces/database.interface';
|
} from 'src/interfaces/database.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { isValidInteger } from 'src/validation';
|
import { isValidInteger } from 'src/validation';
|
||||||
import { DataSource, EntityManager, QueryRunner } from 'typeorm';
|
import { DataSource, EntityManager, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DatabaseRepository implements IDatabaseRepository {
|
export class DatabaseRepository implements IDatabaseRepository {
|
||||||
private vectorExtension: VectorExtension;
|
private vectorExtension: VectorExtension;
|
||||||
|
|
|
@ -24,7 +24,6 @@ import {
|
||||||
} from 'src/interfaces/event.interface';
|
} from 'src/interfaces/event.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { AuthService } from 'src/services/auth.service';
|
import { AuthService } from 'src/services/auth.service';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { handlePromiseError } from 'src/utils/misc';
|
import { handlePromiseError } from 'src/utils/misc';
|
||||||
|
|
||||||
type EmitHandlers = Partial<{ [T in EmitEvent]: Array<EventItem<T>> }>;
|
type EmitHandlers = Partial<{ [T in EmitEvent]: Array<EventItem<T>> }>;
|
||||||
|
@ -37,7 +36,6 @@ type Item<T extends EmitEvent> = {
|
||||||
label: string;
|
label: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@WebSocketGateway({
|
@WebSocketGateway({
|
||||||
cors: true,
|
cors: true,
|
||||||
path: '/api/socket.io',
|
path: '/api/socket.io',
|
||||||
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
QueueStatus,
|
QueueStatus,
|
||||||
} from 'src/interfaces/job.interface';
|
} from 'src/interfaces/job.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
|
|
||||||
export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
|
export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
|
||||||
// misc
|
// misc
|
||||||
|
@ -99,7 +98,6 @@ export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
|
||||||
[JobName.QUEUE_TRASH_EMPTY]: QueueName.BACKGROUND_TASK,
|
[JobName.QUEUE_TRASH_EMPTY]: QueueName.BACKGROUND_TASK,
|
||||||
};
|
};
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JobRepository implements IJobRepository {
|
export class JobRepository implements IJobRepository {
|
||||||
private workers: Partial<Record<QueueName, Worker>> = {};
|
private workers: Partial<Record<QueueName, Worker>> = {};
|
||||||
|
|
|
@ -4,11 +4,9 @@ import { DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { LibraryStatsResponseDto } from 'src/dtos/library.dto';
|
import { LibraryStatsResponseDto } from 'src/dtos/library.dto';
|
||||||
import { LibraryEntity } from 'src/entities/library.entity';
|
import { LibraryEntity } from 'src/entities/library.entity';
|
||||||
import { ILibraryRepository } from 'src/interfaces/library.interface';
|
import { ILibraryRepository } from 'src/interfaces/library.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { IsNull, Not } from 'typeorm';
|
import { IsNull, Not } from 'typeorm';
|
||||||
import { Repository } from 'typeorm/repository/Repository.js';
|
import { Repository } from 'typeorm/repository/Repository.js';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LibraryRepository implements ILibraryRepository {
|
export class LibraryRepository implements ILibraryRepository {
|
||||||
constructor(@InjectRepository(LibraryEntity) private repository: Repository<LibraryEntity>) {}
|
constructor(@InjectRepository(LibraryEntity) private repository: Repository<LibraryEntity>) {}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { ConsoleLogger, Inject, Injectable, Scope } from '@nestjs/common';
|
import { ConsoleLogger, Inject, Injectable, Scope } from '@nestjs/common';
|
||||||
import { isLogLevelEnabled } from '@nestjs/common/services/utils/is-log-level-enabled.util';
|
import { isLogLevelEnabled } from '@nestjs/common/services/utils/is-log-level-enabled.util';
|
||||||
import { ClsService } from 'nestjs-cls';
|
import { ClsService } from 'nestjs-cls';
|
||||||
|
import { Telemetry } from 'src/decorators';
|
||||||
import { LogLevel } from 'src/enum';
|
import { LogLevel } from 'src/enum';
|
||||||
import { IConfigRepository } from 'src/interfaces/config.interface';
|
import { IConfigRepository } from 'src/interfaces/config.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
|
@ -17,6 +18,7 @@ enum LogColor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({ scope: Scope.TRANSIENT })
|
@Injectable({ scope: Scope.TRANSIENT })
|
||||||
|
@Telemetry({ enabled: false })
|
||||||
export class LoggerRepository extends ConsoleLogger implements ILoggerRepository {
|
export class LoggerRepository extends ConsoleLogger implements ILoggerRepository {
|
||||||
private static logLevels: LogLevel[] = [LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
|
private static logLevels: LogLevel[] = [LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
|
||||||
private noColor: boolean;
|
private noColor: boolean;
|
||||||
|
|
|
@ -12,11 +12,9 @@ import {
|
||||||
ModelTask,
|
ModelTask,
|
||||||
ModelType,
|
ModelType,
|
||||||
} from 'src/interfaces/machine-learning.interface';
|
} from 'src/interfaces/machine-learning.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
|
|
||||||
const errorPrefix = 'Machine learning request';
|
const errorPrefix = 'Machine learning request';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MachineLearningRepository implements IMachineLearningRepository {
|
export class MachineLearningRepository implements IMachineLearningRepository {
|
||||||
private async predict<T>(url: string, payload: ModelPayload, config: MachineLearningRequest): Promise<T> {
|
private async predict<T>(url: string, payload: ModelPayload, config: MachineLearningRequest): Promise<T> {
|
||||||
|
|
|
@ -20,11 +20,9 @@ import {
|
||||||
} from 'src/interfaces/map.interface';
|
} from 'src/interfaces/map.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { OptionalBetween } from 'src/utils/database';
|
import { OptionalBetween } from 'src/utils/database';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { DataSource, In, IsNull, Not, QueryRunner, Repository } from 'typeorm';
|
import { DataSource, In, IsNull, Not, QueryRunner, Repository } from 'typeorm';
|
||||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MapRepository implements IMapRepository {
|
export class MapRepository implements IMapRepository {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
TranscodeCommand,
|
TranscodeCommand,
|
||||||
VideoInfo,
|
VideoInfo,
|
||||||
} from 'src/interfaces/media.interface';
|
} from 'src/interfaces/media.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { handlePromiseError } from 'src/utils/misc';
|
import { handlePromiseError } from 'src/utils/misc';
|
||||||
|
|
||||||
const probe = (input: string, options: string[]): Promise<FfprobeData> =>
|
const probe = (input: string, options: string[]): Promise<FfprobeData> =>
|
||||||
|
@ -36,7 +35,6 @@ type ProgressEvent = {
|
||||||
percent?: number;
|
percent?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MediaRepository implements IMediaRepository {
|
export class MediaRepository implements IMediaRepository {
|
||||||
constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
|
constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
|
||||||
|
|
|
@ -3,10 +3,8 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
|
import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { MemoryEntity } from 'src/entities/memory.entity';
|
import { MemoryEntity } from 'src/entities/memory.entity';
|
||||||
import { IMemoryRepository } from 'src/interfaces/memory.interface';
|
import { IMemoryRepository } from 'src/interfaces/memory.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { DataSource, In, Repository } from 'typeorm';
|
import { DataSource, In, Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MemoryRepository implements IMemoryRepository {
|
export class MemoryRepository implements IMemoryRepository {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -3,9 +3,7 @@ import { DefaultReadTaskOptions, ExifTool, Tags } from 'exiftool-vendored';
|
||||||
import geotz from 'geo-tz';
|
import geotz from 'geo-tz';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface';
|
import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MetadataRepository implements IMetadataRepository {
|
export class MetadataRepository implements IMetadataRepository {
|
||||||
private exiftool = new ExifTool({
|
private exiftool = new ExifTool({
|
||||||
|
|
|
@ -4,10 +4,8 @@ import { DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { MoveEntity } from 'src/entities/move.entity';
|
import { MoveEntity } from 'src/entities/move.entity';
|
||||||
import { PathType } from 'src/enum';
|
import { PathType } from 'src/enum';
|
||||||
import { IMoveRepository, MoveCreate } from 'src/interfaces/move.interface';
|
import { IMoveRepository, MoveCreate } from 'src/interfaces/move.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MoveRepository implements IMoveRepository {
|
export class MoveRepository implements IMoveRepository {
|
||||||
constructor(@InjectRepository(MoveEntity) private repository: Repository<MoveEntity>) {}
|
constructor(@InjectRepository(MoveEntity) private repository: Repository<MoveEntity>) {}
|
||||||
|
|
|
@ -15,9 +15,7 @@ import {
|
||||||
SendEmailResponse,
|
SendEmailResponse,
|
||||||
SmtpOptions,
|
SmtpOptions,
|
||||||
} from 'src/interfaces/notification.interface';
|
} from 'src/interfaces/notification.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NotificationRepository implements INotificationRepository {
|
export class NotificationRepository implements INotificationRepository {
|
||||||
constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
|
constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
|
||||||
|
|
|
@ -2,9 +2,7 @@ import { Inject, Injectable, InternalServerErrorException } from '@nestjs/common
|
||||||
import { custom, generators, Issuer } from 'openid-client';
|
import { custom, generators, Issuer } from 'openid-client';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { IOAuthRepository, OAuthConfig, OAuthProfile } from 'src/interfaces/oauth.interface';
|
import { IOAuthRepository, OAuthConfig, OAuthProfile } from 'src/interfaces/oauth.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OAuthRepository implements IOAuthRepository {
|
export class OAuthRepository implements IOAuthRepository {
|
||||||
constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
|
constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
|
||||||
|
|
|
@ -2,10 +2,8 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { PartnerEntity } from 'src/entities/partner.entity';
|
import { PartnerEntity } from 'src/entities/partner.entity';
|
||||||
import { IPartnerRepository, PartnerIds } from 'src/interfaces/partner.interface';
|
import { IPartnerRepository, PartnerIds } from 'src/interfaces/partner.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { DeepPartial, Repository } from 'typeorm';
|
import { DeepPartial, Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PartnerRepository implements IPartnerRepository {
|
export class PartnerRepository implements IPartnerRepository {
|
||||||
constructor(@InjectRepository(PartnerEntity) private repository: Repository<PartnerEntity>) {}
|
constructor(@InjectRepository(PartnerEntity) private repository: Repository<PartnerEntity>) {}
|
||||||
|
|
|
@ -20,11 +20,9 @@ import {
|
||||||
UnassignFacesOptions,
|
UnassignFacesOptions,
|
||||||
UpdateFacesData,
|
UpdateFacesData,
|
||||||
} from 'src/interfaces/person.interface';
|
} from 'src/interfaces/person.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { Paginated, PaginationOptions, paginate, paginatedBuilder } from 'src/utils/pagination';
|
import { Paginated, PaginationOptions, paginate, paginatedBuilder } from 'src/utils/pagination';
|
||||||
import { DataSource, FindManyOptions, FindOptionsRelations, FindOptionsSelect, In, Repository } from 'typeorm';
|
import { DataSource, FindManyOptions, FindOptionsRelations, FindOptionsSelect, In, Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PersonRepository implements IPersonRepository {
|
export class PersonRepository implements IPersonRepository {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -23,12 +23,10 @@ import {
|
||||||
SmartSearchOptions,
|
SmartSearchOptions,
|
||||||
} from 'src/interfaces/search.interface';
|
} from 'src/interfaces/search.interface';
|
||||||
import { asVector, searchAssetBuilder } from 'src/utils/database';
|
import { asVector, searchAssetBuilder } from 'src/utils/database';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { Paginated, PaginationResult, paginatedBuilder } from 'src/utils/pagination';
|
import { Paginated, PaginationResult, paginatedBuilder } from 'src/utils/pagination';
|
||||||
import { isValidInteger } from 'src/validation';
|
import { isValidInteger } from 'src/validation';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchRepository implements ISearchRepository {
|
export class SearchRepository implements ISearchRepository {
|
||||||
private vectorExtension: VectorExtension;
|
private vectorExtension: VectorExtension;
|
||||||
|
|
|
@ -7,7 +7,6 @@ import sharp from 'sharp';
|
||||||
import { IConfigRepository } from 'src/interfaces/config.interface';
|
import { IConfigRepository } from 'src/interfaces/config.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { GitHubRelease, IServerInfoRepository, ServerBuildVersions } from 'src/interfaces/server-info.interface';
|
import { GitHubRelease, IServerInfoRepository, ServerBuildVersions } from 'src/interfaces/server-info.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
|
|
||||||
const exec = promisify(execCallback);
|
const exec = promisify(execCallback);
|
||||||
const maybeFirstLine = async (command: string): Promise<string> => {
|
const maybeFirstLine = async (command: string): Promise<string> => {
|
||||||
|
@ -34,7 +33,6 @@ const getLockfileVersion = (name: string, lockfile?: BuildLockfile) => {
|
||||||
return item?.version;
|
return item?.version;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerInfoRepository implements IServerInfoRepository {
|
export class ServerInfoRepository implements IServerInfoRepository {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -3,10 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { SessionEntity } from 'src/entities/session.entity';
|
import { SessionEntity } from 'src/entities/session.entity';
|
||||||
import { ISessionRepository, SessionSearchOptions } from 'src/interfaces/session.interface';
|
import { ISessionRepository, SessionSearchOptions } from 'src/interfaces/session.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { LessThanOrEqual, Repository } from 'typeorm';
|
import { LessThanOrEqual, Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SessionRepository implements ISessionRepository {
|
export class SessionRepository implements ISessionRepository {
|
||||||
constructor(@InjectRepository(SessionEntity) private repository: Repository<SessionEntity>) {}
|
constructor(@InjectRepository(SessionEntity) private repository: Repository<SessionEntity>) {}
|
||||||
|
|
|
@ -3,10 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SharedLinkRepository implements ISharedLinkRepository {
|
export class SharedLinkRepository implements ISharedLinkRepository {
|
||||||
constructor(@InjectRepository(SharedLinkEntity) private repository: Repository<SharedLinkEntity>) {}
|
constructor(@InjectRepository(SharedLinkEntity) private repository: Repository<SharedLinkEntity>) {}
|
||||||
|
|
|
@ -3,10 +3,8 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { StackEntity } from 'src/entities/stack.entity';
|
import { StackEntity } from 'src/entities/stack.entity';
|
||||||
import { IStackRepository, StackSearch } from 'src/interfaces/stack.interface';
|
import { IStackRepository, StackSearch } from 'src/interfaces/stack.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { DataSource, In, Repository } from 'typeorm';
|
import { DataSource, In, Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class StackRepository implements IStackRepository {
|
export class StackRepository implements IStackRepository {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -14,10 +14,8 @@ import {
|
||||||
ImmichZipStream,
|
ImmichZipStream,
|
||||||
WatchEvents,
|
WatchEvents,
|
||||||
} from 'src/interfaces/storage.interface';
|
} from 'src/interfaces/storage.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { mimeTypes } from 'src/utils/mime-types';
|
import { mimeTypes } from 'src/utils/mime-types';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class StorageRepository implements IStorageRepository {
|
export class StorageRepository implements IStorageRepository {
|
||||||
constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
|
constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
|
||||||
|
|
|
@ -3,10 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import { SystemMetadata, SystemMetadataEntity } from 'src/entities/system-metadata.entity';
|
import { SystemMetadata, SystemMetadataEntity } from 'src/entities/system-metadata.entity';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SystemMetadataRepository implements ISystemMetadataRepository {
|
export class SystemMetadataRepository implements ISystemMetadataRepository {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -4,10 +4,8 @@ import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { TagEntity } from 'src/entities/tag.entity';
|
import { TagEntity } from 'src/entities/tag.entity';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { AssetTagItem, ITagRepository } from 'src/interfaces/tag.interface';
|
import { AssetTagItem, ITagRepository } from 'src/interfaces/tag.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { DataSource, In, Repository, TreeRepository } from 'typeorm';
|
import { DataSource, In, Repository, TreeRepository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TagRepository implements ITagRepository {
|
export class TagRepository implements ITagRepository {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -1,7 +1,22 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
import { MetricOptions } from '@opentelemetry/api';
|
import { MetricOptions } from '@opentelemetry/api';
|
||||||
|
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
|
||||||
|
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
|
||||||
|
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
||||||
|
import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis';
|
||||||
|
import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core';
|
||||||
|
import { PgInstrumentation } from '@opentelemetry/instrumentation-pg';
|
||||||
|
import { NodeSDK, contextBase, metrics, resources } from '@opentelemetry/sdk-node';
|
||||||
|
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
|
||||||
|
import { ClassConstructor } from 'class-transformer';
|
||||||
|
import { snakeCase, startCase } from 'lodash';
|
||||||
import { MetricService } from 'nestjs-otel';
|
import { MetricService } from 'nestjs-otel';
|
||||||
|
import { copyMetadataFromFunctionToFunction } from 'nestjs-otel/lib/opentelemetry.utils';
|
||||||
|
import { serverVersion } from 'src/constants';
|
||||||
|
import { MetadataKey } from 'src/enum';
|
||||||
import { IConfigRepository } from 'src/interfaces/config.interface';
|
import { IConfigRepository } from 'src/interfaces/config.interface';
|
||||||
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { IMetricGroupRepository, ITelemetryRepository, MetricGroupOptions } from 'src/interfaces/telemetry.interface';
|
import { IMetricGroupRepository, ITelemetryRepository, MetricGroupOptions } from 'src/interfaces/telemetry.interface';
|
||||||
|
|
||||||
class MetricGroupRepository implements IMetricGroupRepository {
|
class MetricGroupRepository implements IMetricGroupRepository {
|
||||||
|
@ -33,6 +48,43 @@ class MetricGroupRepository implements IMetricGroupRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const aggregation = new metrics.ExplicitBucketHistogramAggregation(
|
||||||
|
[0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10_000],
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
let instance: NodeSDK | undefined;
|
||||||
|
|
||||||
|
export const bootstrapTelemetry = (port: number) => {
|
||||||
|
if (instance) {
|
||||||
|
throw new Error('OpenTelemetry SDK already started');
|
||||||
|
}
|
||||||
|
instance = new NodeSDK({
|
||||||
|
resource: new resources.Resource({
|
||||||
|
[SemanticResourceAttributes.SERVICE_NAME]: `immich`,
|
||||||
|
[SemanticResourceAttributes.SERVICE_VERSION]: serverVersion.toString(),
|
||||||
|
}),
|
||||||
|
metricReader: new PrometheusExporter({ port }),
|
||||||
|
contextManager: new AsyncLocalStorageContextManager(),
|
||||||
|
instrumentations: [
|
||||||
|
new HttpInstrumentation(),
|
||||||
|
new IORedisInstrumentation(),
|
||||||
|
new NestInstrumentation(),
|
||||||
|
new PgInstrumentation(),
|
||||||
|
],
|
||||||
|
views: [new metrics.View({ aggregation, instrumentName: '*', instrumentUnit: 'ms' })],
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const teardownTelemetry = async () => {
|
||||||
|
if (instance) {
|
||||||
|
await instance.shutdown();
|
||||||
|
instance = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TelemetryRepository implements ITelemetryRepository {
|
export class TelemetryRepository implements ITelemetryRepository {
|
||||||
api: MetricGroupRepository;
|
api: MetricGroupRepository;
|
||||||
|
@ -40,8 +92,13 @@ export class TelemetryRepository implements ITelemetryRepository {
|
||||||
jobs: MetricGroupRepository;
|
jobs: MetricGroupRepository;
|
||||||
repo: MetricGroupRepository;
|
repo: MetricGroupRepository;
|
||||||
|
|
||||||
constructor(metricService: MetricService, @Inject(IConfigRepository) configRepository: IConfigRepository) {
|
constructor(
|
||||||
const { telemetry } = configRepository.getEnv();
|
private metricService: MetricService,
|
||||||
|
private reflect: Reflector,
|
||||||
|
@Inject(IConfigRepository) private configRepository: IConfigRepository,
|
||||||
|
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
||||||
|
) {
|
||||||
|
const { telemetry } = this.configRepository.getEnv();
|
||||||
const { apiMetrics, hostMetrics, jobMetrics, repoMetrics } = telemetry;
|
const { apiMetrics, hostMetrics, jobMetrics, repoMetrics } = telemetry;
|
||||||
|
|
||||||
this.api = new MetricGroupRepository(metricService).configure({ enabled: apiMetrics });
|
this.api = new MetricGroupRepository(metricService).configure({ enabled: apiMetrics });
|
||||||
|
@ -49,4 +106,61 @@ export class TelemetryRepository implements ITelemetryRepository {
|
||||||
this.jobs = new MetricGroupRepository(metricService).configure({ enabled: jobMetrics });
|
this.jobs = new MetricGroupRepository(metricService).configure({ enabled: jobMetrics });
|
||||||
this.repo = new MetricGroupRepository(metricService).configure({ enabled: repoMetrics });
|
this.repo = new MetricGroupRepository(metricService).configure({ enabled: repoMetrics });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setup({ repositories }: { repositories: ClassConstructor<unknown>[] }) {
|
||||||
|
const { telemetry } = this.configRepository.getEnv();
|
||||||
|
if (!telemetry.enabled || !telemetry.repoMetrics) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const Repository of repositories) {
|
||||||
|
const isEnabled = this.reflect.get(MetadataKey.TELEMETRY_ENABLED, Repository) ?? true;
|
||||||
|
if (!isEnabled) {
|
||||||
|
this.logger.debug(`Telemetry disabled for ${Repository.name}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.wrap(Repository);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private wrap(Repository: ClassConstructor<unknown>) {
|
||||||
|
const className = Repository.name;
|
||||||
|
const descriptors = Object.getOwnPropertyDescriptors(Repository.prototype);
|
||||||
|
const unit = 'ms';
|
||||||
|
|
||||||
|
for (const [propName, descriptor] of Object.entries(descriptors)) {
|
||||||
|
const isMethod = typeof descriptor.value == 'function' && propName !== 'constructor';
|
||||||
|
if (!isMethod) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const method = descriptor.value;
|
||||||
|
const propertyName = snakeCase(String(propName));
|
||||||
|
const metricName = `${snakeCase(className).replaceAll(/_(?=(repository)|(controller)|(provider)|(service)|(module))/g, '.')}.${propertyName}.duration`;
|
||||||
|
|
||||||
|
const histogram = this.metricService.getHistogram(metricName, {
|
||||||
|
prefix: 'immich',
|
||||||
|
description: `The elapsed time in ${unit} for the ${startCase(className)} to ${propertyName.toLowerCase()}`,
|
||||||
|
unit,
|
||||||
|
valueType: contextBase.ValueType.DOUBLE,
|
||||||
|
});
|
||||||
|
|
||||||
|
descriptor.value = function (...args: any[]) {
|
||||||
|
const start = performance.now();
|
||||||
|
const result = method.apply(this, args);
|
||||||
|
|
||||||
|
void Promise.resolve(result)
|
||||||
|
.then(() => histogram.record(performance.now() - start, {}))
|
||||||
|
.catch(() => {
|
||||||
|
// noop
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
copyMetadataFromFunctionToFunction(method, descriptor.value);
|
||||||
|
Object.defineProperty(Repository.prototype, propName, descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,8 @@ import {
|
||||||
UserListFilter,
|
UserListFilter,
|
||||||
UserStatsQueryResponse,
|
UserStatsQueryResponse,
|
||||||
} from 'src/interfaces/user.interface';
|
} from 'src/interfaces/user.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { IsNull, Not, Repository } from 'typeorm';
|
import { IsNull, Not, Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserRepository implements IUserRepository {
|
export class UserRepository implements IUserRepository {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -2,10 +2,8 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { VersionHistoryEntity } from 'src/entities/version-history.entity';
|
import { VersionHistoryEntity } from 'src/entities/version-history.entity';
|
||||||
import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface';
|
import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Instrumentation()
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VersionHistoryRepository implements IVersionHistoryRepository {
|
export class VersionHistoryRepository implements IVersionHistoryRepository {
|
||||||
constructor(@InjectRepository(VersionHistoryEntity) private repository: Repository<VersionHistoryEntity>) {}
|
constructor(@InjectRepository(VersionHistoryEntity) private repository: Repository<VersionHistoryEntity>) {}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { TagService } from 'src/services/tag.service';
|
||||||
import { TrashService } from 'src/services/trash.service';
|
import { TrashService } from 'src/services/trash.service';
|
||||||
import { UserService } from 'src/services/user.service';
|
import { UserService } from 'src/services/user.service';
|
||||||
import { VersionService } from 'src/services/version.service';
|
import { VersionService } from 'src/services/version.service';
|
||||||
import { otelShutdown } from 'src/utils/instrumentation';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MicroservicesService {
|
export class MicroservicesService {
|
||||||
|
@ -101,8 +100,4 @@ export class MicroservicesService {
|
||||||
[JobName.QUEUE_TRASH_EMPTY]: () => this.trashService.handleQueueEmptyTrash(),
|
[JobName.QUEUE_TRASH_EMPTY]: () => this.trashService.handleQueueEmptyTrash(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async onShutdown() {
|
|
||||||
await otelShutdown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
|
|
||||||
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
|
|
||||||
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
|
||||||
import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis';
|
|
||||||
import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core';
|
|
||||||
import { PgInstrumentation } from '@opentelemetry/instrumentation-pg';
|
|
||||||
import { NodeSDK, contextBase, metrics, resources } from '@opentelemetry/sdk-node';
|
|
||||||
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
|
|
||||||
import { snakeCase, startCase } from 'lodash';
|
|
||||||
import { copyMetadataFromFunctionToFunction } from 'nestjs-otel/lib/opentelemetry.utils';
|
|
||||||
import { performance } from 'node:perf_hooks';
|
|
||||||
import { serverVersion } from 'src/constants';
|
|
||||||
import { DecorateAll } from 'src/decorators';
|
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
|
||||||
|
|
||||||
const aggregation = new metrics.ExplicitBucketHistogramAggregation(
|
|
||||||
[0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10_000],
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { telemetry } = new ConfigRepository().getEnv();
|
|
||||||
|
|
||||||
let otelSingleton: NodeSDK | undefined;
|
|
||||||
|
|
||||||
export const otelStart = (port: number) => {
|
|
||||||
if (otelSingleton) {
|
|
||||||
throw new Error('OpenTelemetry SDK already started');
|
|
||||||
}
|
|
||||||
otelSingleton = new NodeSDK({
|
|
||||||
resource: new resources.Resource({
|
|
||||||
[SemanticResourceAttributes.SERVICE_NAME]: `immich`,
|
|
||||||
[SemanticResourceAttributes.SERVICE_VERSION]: serverVersion.toString(),
|
|
||||||
}),
|
|
||||||
metricReader: new PrometheusExporter({ port }),
|
|
||||||
contextManager: new AsyncLocalStorageContextManager(),
|
|
||||||
instrumentations: [
|
|
||||||
new HttpInstrumentation(),
|
|
||||||
new IORedisInstrumentation(),
|
|
||||||
new NestInstrumentation(),
|
|
||||||
new PgInstrumentation(),
|
|
||||||
],
|
|
||||||
views: [new metrics.View({ aggregation, instrumentName: '*', instrumentUnit: 'ms' })],
|
|
||||||
});
|
|
||||||
otelSingleton.start();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const otelShutdown = async () => {
|
|
||||||
if (otelSingleton) {
|
|
||||||
await otelSingleton.shutdown();
|
|
||||||
otelSingleton = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function ExecutionTimeHistogram({
|
|
||||||
description,
|
|
||||||
unit = 'ms',
|
|
||||||
valueType = contextBase.ValueType.DOUBLE,
|
|
||||||
}: contextBase.MetricOptions = {}) {
|
|
||||||
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
||||||
if (!telemetry.repoMetrics || process.env.OTEL_SDK_DISABLED) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const method = descriptor.value;
|
|
||||||
const className = target.constructor.name as string;
|
|
||||||
const propertyName = String(propertyKey);
|
|
||||||
const metricName = `${snakeCase(className).replaceAll(/_(?=(repository)|(controller)|(provider)|(service)|(module))/g, '.')}.${snakeCase(propertyName)}.duration`;
|
|
||||||
|
|
||||||
const metricDescription =
|
|
||||||
description ??
|
|
||||||
`The elapsed time in ${unit} for the ${startCase(className)} to ${startCase(propertyName).toLowerCase()}`;
|
|
||||||
|
|
||||||
let histogram: contextBase.Histogram | undefined;
|
|
||||||
|
|
||||||
descriptor.value = function (...args: any[]) {
|
|
||||||
const start = performance.now();
|
|
||||||
const result = method.apply(this, args);
|
|
||||||
|
|
||||||
void Promise.resolve(result)
|
|
||||||
.then(() => {
|
|
||||||
const end = performance.now();
|
|
||||||
if (!histogram) {
|
|
||||||
histogram = contextBase.metrics
|
|
||||||
.getMeter('immich')
|
|
||||||
.createHistogram(metricName, { description: metricDescription, unit, valueType });
|
|
||||||
}
|
|
||||||
histogram.record(end - start, {});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// noop
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
copyMetadataFromFunctionToFunction(method, descriptor.value);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Instrumentation = () => DecorateAll(ExecutionTimeHistogram());
|
|
|
@ -11,16 +11,18 @@ import { IConfigRepository } from 'src/interfaces/config.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
|
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
|
import { bootstrapTelemetry } from 'src/repositories/telemetry.repository';
|
||||||
import { ApiService } from 'src/services/api.service';
|
import { ApiService } from 'src/services/api.service';
|
||||||
import { isStartUpError } from 'src/services/storage.service';
|
import { isStartUpError } from 'src/services/storage.service';
|
||||||
import { otelStart } from 'src/utils/instrumentation';
|
|
||||||
import { useSwagger } from 'src/utils/misc';
|
import { useSwagger } from 'src/utils/misc';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
process.title = 'immich-api';
|
process.title = 'immich-api';
|
||||||
|
|
||||||
const { telemetry, network } = new ConfigRepository().getEnv();
|
const { telemetry, network } = new ConfigRepository().getEnv();
|
||||||
otelStart(telemetry.apiPort);
|
if (telemetry.enabled) {
|
||||||
|
bootstrapTelemetry(telemetry.apiPort);
|
||||||
|
}
|
||||||
|
|
||||||
const app = await NestFactory.create<NestExpressApplication>(ApiModule, { bufferLogs: true });
|
const app = await NestFactory.create<NestExpressApplication>(ApiModule, { bufferLogs: true });
|
||||||
const logger = await app.resolve<ILoggerRepository>(ILoggerRepository);
|
const logger = await app.resolve<ILoggerRepository>(ILoggerRepository);
|
||||||
|
|
|
@ -6,12 +6,14 @@ import { IConfigRepository } from 'src/interfaces/config.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
|
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
|
import { bootstrapTelemetry } from 'src/repositories/telemetry.repository';
|
||||||
import { isStartUpError } from 'src/services/storage.service';
|
import { isStartUpError } from 'src/services/storage.service';
|
||||||
import { otelStart } from 'src/utils/instrumentation';
|
|
||||||
|
|
||||||
export async function bootstrap() {
|
export async function bootstrap() {
|
||||||
const { telemetry } = new ConfigRepository().getEnv();
|
const { telemetry } = new ConfigRepository().getEnv();
|
||||||
otelStart(telemetry.microservicesPort);
|
if (telemetry.enabled) {
|
||||||
|
bootstrapTelemetry(telemetry.microservicesPort);
|
||||||
|
}
|
||||||
|
|
||||||
const app = await NestFactory.create(MicroservicesModule, { bufferLogs: true });
|
const app = await NestFactory.create(MicroservicesModule, { bufferLogs: true });
|
||||||
const logger = await app.resolve(ILoggerRepository);
|
const logger = await app.resolve(ILoggerRepository);
|
||||||
|
|
|
@ -12,6 +12,7 @@ const newMetricGroupMock = () => {
|
||||||
|
|
||||||
export const newTelemetryRepositoryMock = (): Mocked<ITelemetryRepository> => {
|
export const newTelemetryRepositoryMock = (): Mocked<ITelemetryRepository> => {
|
||||||
return {
|
return {
|
||||||
|
setup: vitest.fn(),
|
||||||
api: newMetricGroupMock(),
|
api: newMetricGroupMock(),
|
||||||
host: newMetricGroupMock(),
|
host: newMetricGroupMock(),
|
||||||
jobs: newMetricGroupMock(),
|
jobs: newMetricGroupMock(),
|
||||||
|
|
Loading…
Reference in a new issue