1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-10 13:56:47 +01:00
immich/server/src/services/system-config.service.ts

145 lines
5 KiB
TypeScript
Raw Normal View History

import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { instanceToPlain } from 'class-transformer';
import _ from 'lodash';
import {
supportedDayTokens,
supportedHourTokens,
supportedMinuteTokens,
supportedMonthTokens,
supportedPresetTokens,
supportedSecondTokens,
supportedWeekTokens,
supportedYearTokens,
2024-03-21 04:15:09 +01:00
} from 'src/constants';
import { SystemConfigCore } from 'src/cores/system-config.core';
import { OnEventInternal } from 'src/decorators';
import { SystemConfigDto, SystemConfigTemplateStorageOptionDto, mapConfig } from 'src/dtos/system-config.dto';
2024-03-20 22:02:51 +01:00
import { LogLevel, SystemConfig } from 'src/entities/system-config.entity';
import {
ClientEvent,
ICommunicationRepository,
InternalEvent,
InternalEventMap,
ServerEvent,
} from 'src/interfaces/communication.interface';
import { ISearchRepository } from 'src/interfaces/search.interface';
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
2024-03-21 04:15:09 +01:00
import { ImmichLogger } from 'src/utils/logger';
@Injectable()
export class SystemConfigService {
private logger = new ImmichLogger(SystemConfigService.name);
private core: SystemConfigCore;
constructor(
@Inject(ISystemConfigRepository) private repository: ISystemConfigRepository,
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
@Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository,
@Inject(ISearchRepository) private smartInfoRepository: ISearchRepository,
) {
this.core = SystemConfigCore.create(repository);
this.communicationRepository.on(ServerEvent.CONFIG_UPDATE, () => this.handleConfigUpdate());
this.core.config$.subscribe((config) => this.setLogLevel(config));
}
async init() {
const config = await this.core.getConfig();
this.config$.next(config);
}
get config$() {
return this.core.config$;
}
async getConfig(): Promise<SystemConfigDto> {
const config = await this.core.getConfig();
return mapConfig(config);
}
getDefaults(): SystemConfigDto {
const config = this.core.getDefaults();
return mapConfig(config);
}
@OnEventInternal(InternalEvent.VALIDATE_CONFIG)
validateConfig({ newConfig, oldConfig }: InternalEventMap[InternalEvent.VALIDATE_CONFIG]) {
if (!_.isEqual(instanceToPlain(newConfig.logging), oldConfig.logging) && this.getEnvLogLevel()) {
throw new Error('Logging cannot be changed while the environment variable LOG_LEVEL is set.');
}
}
async updateConfig(dto: SystemConfigDto): Promise<SystemConfigDto> {
2023-12-08 17:15:46 +01:00
const oldConfig = await this.core.getConfig();
try {
await this.communicationRepository.emitAsync(InternalEvent.VALIDATE_CONFIG, { newConfig: dto, oldConfig });
} catch (error) {
this.logger.warn(`Unable to save system config due to a validation error: ${error}`);
throw new BadRequestException(error instanceof Error ? error.message : error);
}
2023-12-08 17:15:46 +01:00
const newConfig = await this.core.updateConfig(dto);
this.communicationRepository.broadcast(ClientEvent.CONFIG_UPDATE, {});
this.communicationRepository.sendServerEvent(ServerEvent.CONFIG_UPDATE);
2023-12-08 17:15:46 +01:00
if (oldConfig.machineLearning.clip.modelName !== newConfig.machineLearning.clip.modelName) {
await this.smartInfoRepository.init(newConfig.machineLearning.clip.modelName);
}
return mapConfig(newConfig);
}
// this is only used by the cli on config change, and it's not actually needed anymore
async refreshConfig() {
this.communicationRepository.sendServerEvent(ServerEvent.CONFIG_UPDATE);
await this.core.refreshConfig();
return true;
}
getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
const options = new SystemConfigTemplateStorageOptionDto();
options.dayOptions = supportedDayTokens;
options.weekOptions = supportedWeekTokens;
options.monthOptions = supportedMonthTokens;
options.yearOptions = supportedYearTokens;
options.hourOptions = supportedHourTokens;
options.secondOptions = supportedSecondTokens;
options.minuteOptions = supportedMinuteTokens;
options.presetOptions = supportedPresetTokens;
return options;
}
async getMapStyle(theme: 'light' | 'dark') {
const { map } = await this.getConfig();
const styleUrl = theme === 'dark' ? map.darkStyle : map.lightStyle;
if (styleUrl) {
return this.repository.fetchStyle(styleUrl);
}
return JSON.parse(await this.repository.readFile(`./resources/style-${theme}.json`));
}
async getCustomCss(): Promise<string> {
const { theme } = await this.core.getConfig();
return theme.customCss;
}
private async handleConfigUpdate() {
await this.core.refreshConfig();
}
private setLogLevel({ logging }: SystemConfig) {
const envLevel = this.getEnvLogLevel();
const configLevel = logging.enabled ? logging.level : false;
const level = envLevel ?? configLevel;
ImmichLogger.setLogLevel(level);
this.logger.log(`LogLevel=${level} ${envLevel ? '(set via LOG_LEVEL)' : '(set via system config)'}`);
}
private getEnvLogLevel() {
return process.env.LOG_LEVEL as LogLevel;
}
}