mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 22:51:59 +00:00
refactor(server): system config (#1353)
* refactor(server): system config * fix: jest circular import * chore: ignore migrations in coverage report * chore: tests * chore: tests * chore: todo note * chore: remove vite config backup * chore: fix redis hostname
This commit is contained in:
parent
66cd7dd809
commit
c0a6b3d5a3
92 changed files with 842 additions and 614 deletions
|
@ -5,7 +5,7 @@ DB_PASSWORD=postgres
|
|||
DB_DATABASE_NAME=e2e_test
|
||||
|
||||
# Redis
|
||||
REDIS_HOSTNAME=immich_redis_test
|
||||
REDIS_HOSTNAME=immich-redis-test
|
||||
|
||||
# Upload File Config
|
||||
UPLOAD_LOCATION=./upload
|
||||
|
|
|
@ -3,7 +3,6 @@ import { AssetService } from './asset.service';
|
|||
import { AssetController } from './asset.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AssetEntity } from '@app/infra';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { BackgroundTaskModule } from '../../modules/background-task/background-task.module';
|
||||
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
||||
import { CommunicationModule } from '../communication/communication.module';
|
||||
|
@ -12,7 +11,6 @@ import { DownloadModule } from '../../modules/download/download.module';
|
|||
import { TagModule } from '../tag/tag.module';
|
||||
import { AlbumModule } from '../album/album.module';
|
||||
import { StorageModule } from '@app/storage';
|
||||
import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.constant';
|
||||
import { ShareModule } from '../share/share.module';
|
||||
|
||||
const ASSET_REPOSITORY_PROVIDER = {
|
||||
|
@ -29,7 +27,6 @@ const ASSET_REPOSITORY_PROVIDER = {
|
|||
TagModule,
|
||||
StorageModule,
|
||||
forwardRef(() => AlbumModule),
|
||||
BullModule.registerQueue(...immichSharedQueues),
|
||||
ShareModule,
|
||||
],
|
||||
controllers: [AssetController],
|
||||
|
|
|
@ -9,7 +9,7 @@ import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto';
|
|||
import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto';
|
||||
import { DownloadService } from '../../modules/download/download.service';
|
||||
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
||||
import { IAssetUploadedJob, IVideoTranscodeJob } from '@app/job';
|
||||
import { IAssetUploadedJob, IVideoTranscodeJob } from '@app/domain';
|
||||
import { Queue } from 'bull';
|
||||
import { IAlbumRepository } from '../album/album-repository';
|
||||
import { StorageService } from '@app/storage';
|
||||
|
|
|
@ -43,7 +43,7 @@ import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-as
|
|||
import { UpdateAssetDto } from './dto/update-asset.dto';
|
||||
import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto';
|
||||
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
||||
import { IAssetUploadedJob, IVideoTranscodeJob, QueueName, JobName } from '@app/job';
|
||||
import { IAssetUploadedJob, IVideoTranscodeJob, QueueName, JobName } from '@app/domain';
|
||||
import { InjectQueue } from '@nestjs/bull';
|
||||
import { Queue } from 'bull';
|
||||
import { DownloadService } from '../../modules/download/download.service';
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ImmichConfigModule } from '@app/immich-config';
|
||||
import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module';
|
||||
import { OAuthModule } from '../oauth/oauth.module';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Module({
|
||||
imports: [ImmichJwtModule, OAuthModule, ImmichConfigModule],
|
||||
imports: [ImmichJwtModule, OAuthModule],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService],
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@ import { UserEntity } from '@app/infra';
|
|||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { SystemConfig } from '@app/infra';
|
||||
import { ImmichConfigService } from '@app/immich-config';
|
||||
import { SystemConfigService } from '@app/domain';
|
||||
import { AuthType } from '../../constants/jwt.constant';
|
||||
import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
|
||||
import { OAuthService } from '../oauth/oauth.service';
|
||||
|
@ -50,7 +50,7 @@ describe('AuthService', () => {
|
|||
let sut: AuthService;
|
||||
let userRepositoryMock: jest.Mocked<IUserRepository>;
|
||||
let immichJwtServiceMock: jest.Mocked<ImmichJwtService>;
|
||||
let immichConfigServiceMock: jest.Mocked<ImmichConfigService>;
|
||||
let immichConfigServiceMock: jest.Mocked<SystemConfigService>;
|
||||
let oauthServiceMock: jest.Mocked<OAuthService>;
|
||||
let compare: jest.Mock;
|
||||
|
||||
|
@ -89,7 +89,7 @@ describe('AuthService', () => {
|
|||
|
||||
immichConfigServiceMock = {
|
||||
config$: { subscribe: jest.fn() },
|
||||
} as unknown as jest.Mocked<ImmichConfigService>;
|
||||
} as unknown as jest.Mocked<SystemConfigService>;
|
||||
|
||||
sut = new AuthService(
|
||||
oauthServiceMock,
|
||||
|
|
|
@ -20,7 +20,7 @@ import { LoginResponseDto } from './response-dto/login-response.dto';
|
|||
import { LogoutResponseDto } from './response-dto/logout-response.dto';
|
||||
import { OAuthService } from '../oauth/oauth.service';
|
||||
import { UserCore } from '@app/domain';
|
||||
import { ImmichConfigService, INITIAL_SYSTEM_CONFIG } from '@app/immich-config';
|
||||
import { SystemConfigService, INITIAL_SYSTEM_CONFIG } from '@app/domain';
|
||||
import { SystemConfig } from '@app/infra';
|
||||
|
||||
@Injectable()
|
||||
|
@ -32,7 +32,7 @@ export class AuthService {
|
|||
private oauthService: OAuthService,
|
||||
private immichJwtService: ImmichJwtService,
|
||||
@Inject(IUserRepository) userRepository: IUserRepository,
|
||||
private configService: ImmichConfigService,
|
||||
private configService: SystemConfigService,
|
||||
@Inject(INITIAL_SYSTEM_CONFIG) private config: SystemConfig,
|
||||
) {
|
||||
this.userCore = new UserCore(userRepository);
|
||||
|
|
|
@ -6,20 +6,10 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
|||
import { ExifEntity } from '@app/infra';
|
||||
import { TagModule } from '../tag/tag.module';
|
||||
import { AssetModule } from '../asset/asset.module';
|
||||
|
||||
import { StorageModule } from '@app/storage';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.constant';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([ExifEntity]),
|
||||
ImmichJwtModule,
|
||||
TagModule,
|
||||
AssetModule,
|
||||
StorageModule,
|
||||
BullModule.registerQueue(...immichSharedQueues),
|
||||
],
|
||||
imports: [TypeOrmModule.forFeature([ExifEntity]), ImmichJwtModule, TagModule, AssetModule, StorageModule],
|
||||
controllers: [JobController],
|
||||
providers: [JobService],
|
||||
})
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { IMetadataExtractionJob, IThumbnailGenerationJob, IVideoTranscodeJob, QueueName, JobName } from '@app/job';
|
||||
import {
|
||||
IMachineLearningJob,
|
||||
IMetadataExtractionJob,
|
||||
IThumbnailGenerationJob,
|
||||
IVideoTranscodeJob,
|
||||
QueueName,
|
||||
JobName,
|
||||
} from '@app/domain';
|
||||
import { InjectQueue } from '@nestjs/bull';
|
||||
import { Queue } from 'bull';
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
|
@ -7,7 +14,6 @@ import { IAssetRepository } from '../asset/asset-repository';
|
|||
import { AssetType } from '@app/infra';
|
||||
import { GetJobDto, JobId } from './dto/get-job.dto';
|
||||
import { JobStatusResponseDto } from './response-dto/job-status-response.dto';
|
||||
import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface';
|
||||
import { StorageService } from '@app/storage';
|
||||
import { MACHINE_LEARNING_ENABLED } from '@app/common';
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { ImmichConfigModule } from '@app/immich-config';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module';
|
||||
import { OAuthController } from './oauth.controller';
|
||||
import { OAuthService } from './oauth.service';
|
||||
|
||||
@Module({
|
||||
imports: [ImmichJwtModule, ImmichConfigModule],
|
||||
imports: [ImmichJwtModule],
|
||||
controllers: [OAuthController],
|
||||
providers: [OAuthService],
|
||||
exports: [OAuthService],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { SystemConfig, UserEntity } from '@app/infra';
|
||||
import { ImmichConfigService } from '@app/immich-config';
|
||||
import { SystemConfigService } from '@app/domain';
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { generators, Issuer } from 'openid-client';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
|
@ -86,7 +86,7 @@ jest.mock('@nestjs/common', () => ({
|
|||
describe('OAuthService', () => {
|
||||
let sut: OAuthService;
|
||||
let userRepositoryMock: jest.Mocked<IUserRepository>;
|
||||
let immichConfigServiceMock: jest.Mocked<ImmichConfigService>;
|
||||
let immichConfigServiceMock: jest.Mocked<SystemConfigService>;
|
||||
let immichJwtServiceMock: jest.Mocked<ImmichJwtService>;
|
||||
let callbackMock: jest.Mock;
|
||||
|
||||
|
@ -132,7 +132,7 @@ describe('OAuthService', () => {
|
|||
|
||||
immichConfigServiceMock = {
|
||||
config$: { subscribe: jest.fn() },
|
||||
} as unknown as jest.Mocked<ImmichConfigService>;
|
||||
} as unknown as jest.Mocked<SystemConfigService>;
|
||||
|
||||
sut = new OAuthService(immichJwtServiceMock, immichConfigServiceMock, userRepositoryMock, config.disabled);
|
||||
});
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { SystemConfig } from '@app/infra';
|
||||
import { ImmichConfigService, INITIAL_SYSTEM_CONFIG } from '@app/immich-config';
|
||||
import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { ClientMetadata, custom, generators, Issuer, UserinfoResponse } from 'openid-client';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
|
||||
import { LoginResponseDto } from '../auth/response-dto/login-response.dto';
|
||||
import { IUserRepository, UserResponseDto, UserCore } from '@app/domain';
|
||||
import { IUserRepository, UserResponseDto, UserCore, SystemConfigService, INITIAL_SYSTEM_CONFIG } from '@app/domain';
|
||||
import { OAuthCallbackDto } from './dto/oauth-auth-code.dto';
|
||||
import { OAuthConfigDto } from './dto/oauth-config.dto';
|
||||
import { OAuthConfigResponseDto } from './response-dto/oauth-config-response.dto';
|
||||
|
@ -23,7 +22,7 @@ export class OAuthService {
|
|||
|
||||
constructor(
|
||||
private immichJwtService: ImmichJwtService,
|
||||
immichConfigService: ImmichConfigService,
|
||||
configService: SystemConfigService,
|
||||
@Inject(IUserRepository) userRepository: IUserRepository,
|
||||
@Inject(INITIAL_SYSTEM_CONFIG) private config: SystemConfig,
|
||||
) {
|
||||
|
@ -33,7 +32,7 @@ export class OAuthService {
|
|||
timeout: 30000,
|
||||
});
|
||||
|
||||
immichConfigService.config$.subscribe((config) => (this.config = config));
|
||||
configService.config$.subscribe((config) => (this.config = config));
|
||||
}
|
||||
|
||||
public async generateConfig(dto: OAuthConfigDto): Promise<OAuthConfigResponseDto> {
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { SystemConfigEntity } from '@app/infra';
|
||||
import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.constant';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ImmichConfigModule } from 'libs/immich-config/src';
|
||||
import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module';
|
||||
import { SystemConfigController } from './system-config.controller';
|
||||
import { SystemConfigService } from './system-config.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ImmichJwtModule,
|
||||
ImmichConfigModule,
|
||||
TypeOrmModule.forFeature([SystemConfigEntity]),
|
||||
BullModule.registerQueue(...immichSharedQueues),
|
||||
],
|
||||
controllers: [SystemConfigController],
|
||||
providers: [SystemConfigService],
|
||||
})
|
||||
export class SystemConfigModule {}
|
|
@ -1,54 +0,0 @@
|
|||
import { JobName, QueueName } from '@app/job';
|
||||
import {
|
||||
supportedDayTokens,
|
||||
supportedHourTokens,
|
||||
supportedMinuteTokens,
|
||||
supportedMonthTokens,
|
||||
supportedPresetTokens,
|
||||
supportedSecondTokens,
|
||||
supportedYearTokens,
|
||||
} from '@app/storage/constants/supported-datetime-template';
|
||||
import { InjectQueue } from '@nestjs/bull';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Queue } from 'bull';
|
||||
import { ImmichConfigService } from 'libs/immich-config/src';
|
||||
import { mapConfig, SystemConfigDto } from './dto/system-config.dto';
|
||||
import { SystemConfigTemplateStorageOptionDto } from './response-dto/system-config-template-storage-option.dto';
|
||||
|
||||
@Injectable()
|
||||
export class SystemConfigService {
|
||||
constructor(
|
||||
private immichConfigService: ImmichConfigService,
|
||||
@InjectQueue(QueueName.CONFIG) private configQueue: Queue,
|
||||
) {}
|
||||
|
||||
public async getConfig(): Promise<SystemConfigDto> {
|
||||
const config = await this.immichConfigService.getConfig();
|
||||
return mapConfig(config);
|
||||
}
|
||||
|
||||
public getDefaults(): SystemConfigDto {
|
||||
const config = this.immichConfigService.getDefaults();
|
||||
return mapConfig(config);
|
||||
}
|
||||
|
||||
public async updateConfig(dto: SystemConfigDto): Promise<SystemConfigDto> {
|
||||
const config = await this.immichConfigService.updateConfig(dto);
|
||||
this.configQueue.add(JobName.CONFIG_CHANGE, {});
|
||||
return mapConfig(config);
|
||||
}
|
||||
|
||||
public getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
|
||||
const options = new SystemConfigTemplateStorageOptionDto();
|
||||
|
||||
options.dayOptions = supportedDayTokens;
|
||||
options.monthOptions = supportedMonthTokens;
|
||||
options.yearOptions = supportedYearTokens;
|
||||
options.hourOptions = supportedHourTokens;
|
||||
options.minuteOptions = supportedMinuteTokens;
|
||||
options.secondOptions = supportedSecondTokens;
|
||||
options.presetOptions = supportedPresetTokens;
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import { Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
|
||||
import { ApiExcludeEndpoint } from '@nestjs/swagger';
|
||||
import { ImmichConfigService } from '@app/immich-config';
|
||||
import { SystemConfigService } from '@app/domain';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private configService: ImmichConfigService) {}
|
||||
constructor(private configService: SystemConfigService) {}
|
||||
|
||||
@ApiExcludeEndpoint()
|
||||
@Post('refresh-config')
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { immichAppConfig, immichBullAsyncConfig } from '@app/common/config';
|
||||
import { immichAppConfig } from '@app/common/config';
|
||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
||||
import { AssetModule } from './api-v1/asset/asset.module';
|
||||
import { AuthModule } from './api-v1/auth/auth.module';
|
||||
import { ImmichJwtModule } from './modules/immich-jwt/immich-jwt.module';
|
||||
import { DeviceInfoModule } from './api-v1/device-info/device-info.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { ServerInfoModule } from './api-v1/server-info/server-info.module';
|
||||
import { BackgroundTaskModule } from './modules/background-task/background-task.module';
|
||||
import { CommunicationModule } from './api-v1/communication/communication.module';
|
||||
|
@ -14,14 +13,12 @@ import { AppController } from './app.controller';
|
|||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module';
|
||||
import { JobModule } from './api-v1/job/job.module';
|
||||
import { SystemConfigModule } from './api-v1/system-config/system-config.module';
|
||||
import { OAuthModule } from './api-v1/oauth/oauth.module';
|
||||
import { TagModule } from './api-v1/tag/tag.module';
|
||||
import { ImmichConfigModule } from '@app/immich-config';
|
||||
import { ShareModule } from './api-v1/share/share.module';
|
||||
import { DomainModule } from '@app/domain';
|
||||
import { InfraModule } from '@app/infra';
|
||||
import { APIKeyController, UserController } from './controllers';
|
||||
import { APIKeyController, SystemConfigController, UserController } from './controllers';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
@ -37,12 +34,9 @@ import { APIKeyController, UserController } from './controllers';
|
|||
OAuthModule,
|
||||
|
||||
ImmichJwtModule,
|
||||
ImmichConfigModule,
|
||||
|
||||
DeviceInfoModule,
|
||||
|
||||
BullModule.forRootAsync(immichBullAsyncConfig),
|
||||
|
||||
ServerInfoModule,
|
||||
|
||||
BackgroundTaskModule,
|
||||
|
@ -57,8 +51,6 @@ import { APIKeyController, UserController } from './controllers';
|
|||
|
||||
JobModule,
|
||||
|
||||
SystemConfigModule,
|
||||
|
||||
TagModule,
|
||||
|
||||
ShareModule,
|
||||
|
@ -67,6 +59,7 @@ import { APIKeyController, UserController } from './controllers';
|
|||
//
|
||||
AppController,
|
||||
APIKeyController,
|
||||
SystemConfigController,
|
||||
UserController,
|
||||
],
|
||||
providers: [],
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './api-key.controller';
|
||||
export * from './system-config.controller';
|
||||
export * from './user.controller';
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { SystemConfigDto, SystemConfigService, SystemConfigTemplateStorageOptionDto } from '@app/domain';
|
||||
import { Body, Controller, Get, Put, ValidationPipe } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { Authenticated } from '../../decorators/authenticated.decorator';
|
||||
import { SystemConfigTemplateStorageOptionDto } from './response-dto/system-config-template-storage-option.dto';
|
||||
import { SystemConfigDto } from './dto/system-config.dto';
|
||||
import { SystemConfigService } from './system-config.service';
|
||||
import { Authenticated } from '../decorators/authenticated.decorator';
|
||||
|
||||
@ApiTags('System Config')
|
||||
@ApiBearerAuth()
|
|
@ -1,6 +1,6 @@
|
|||
import { BullModule } from '@nestjs/bull';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { QueueName } from '@app/job';
|
||||
import { QueueName } from '@app/domain';
|
||||
import { BackgroundTaskProcessor } from './background-task.processor';
|
||||
import { BackgroundTaskService } from './background-task.service';
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { assetUtils } from '@app/common/utils';
|
||||
import { Process, Processor } from '@nestjs/bull';
|
||||
import { Job } from 'bull';
|
||||
import { JobName, QueueName } from '@app/job';
|
||||
import { JobName, QueueName } from '@app/domain';
|
||||
import { AssetResponseDto } from '../../api-v1/asset/response-dto/asset-response.dto';
|
||||
|
||||
@Processor(QueueName.BACKGROUND_TASK)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { InjectQueue } from '@nestjs/bull/dist/decorators';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Queue } from 'bull';
|
||||
import { JobName, QueueName } from '@app/job';
|
||||
import { JobName, QueueName } from '@app/domain';
|
||||
import { AssetResponseDto } from '../../api-v1/asset/response-dto/asset-response.dto';
|
||||
|
||||
@Injectable()
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
import { BullModule } from '@nestjs/bull';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AssetEntity, ExifEntity, UserEntity } from '@app/infra';
|
||||
import { ScheduleTasksService } from './schedule-tasks.service';
|
||||
import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.constant';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([AssetEntity, ExifEntity, UserEntity]),
|
||||
BullModule.registerQueue(...immichSharedQueues),
|
||||
],
|
||||
imports: [TypeOrmModule.forFeature([AssetEntity, ExifEntity, UserEntity])],
|
||||
providers: [ScheduleTasksService],
|
||||
})
|
||||
export class ScheduleTasksModule {}
|
||||
|
|
|
@ -5,9 +5,9 @@ import { IsNull, Not, Repository } from 'typeorm';
|
|||
import { AssetEntity, AssetType, ExifEntity, UserEntity } from '@app/infra';
|
||||
import { InjectQueue } from '@nestjs/bull';
|
||||
import { Queue } from 'bull';
|
||||
import { IMetadataExtractionJob, IVideoTranscodeJob, QueueName, JobName } from '@app/job';
|
||||
import { IMetadataExtractionJob, IVideoTranscodeJob, QueueName, JobName } from '@app/domain';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { IUserDeletionJob } from '@app/job/interfaces/user-deletion.interface';
|
||||
import { IUserDeletionJob } from '@app/domain';
|
||||
import { userUtils } from '@app/common';
|
||||
|
||||
@Injectable()
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
},
|
||||
"moduleNameMapper": {
|
||||
"^@app/common": "<rootDir>../../../libs/common/src",
|
||||
"^@app/job(|/.*)$": "<rootDir>../../../libs/job/src/$1",
|
||||
"^@app/immich-config(|/.*)$": "<rootDir>../../../libs/immich-config/src/$1",
|
||||
"^@app/storage(|/.*)$": "<rootDir>../../../libs/storage/src/$1",
|
||||
"^@app/infra(|/.*)$": "<rootDir>../../../libs/infra/src/$1",
|
||||
"^@app/domain(|/.*)$": "<rootDir>../../../libs/domain/src/$1"
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { immichAppConfig, immichBullAsyncConfig } from '@app/common/config';
|
||||
import { immichAppConfig } from '@app/common/config';
|
||||
import { AssetEntity, ExifEntity, SmartInfoEntity, UserEntity, APIKeyEntity, InfraModule } from '@app/infra';
|
||||
import { StorageModule } from '@app/storage';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ImmichConfigModule } from 'libs/immich-config/src';
|
||||
import { CommunicationModule } from '../../immich/src/api-v1/communication/communication.module';
|
||||
import { MicroservicesService } from './microservices.service';
|
||||
import { AssetUploadedProcessor } from './processors/asset-uploaded.processor';
|
||||
|
@ -16,7 +14,6 @@ import { StorageMigrationProcessor } from './processors/storage-migration.proces
|
|||
import { ThumbnailGeneratorProcessor } from './processors/thumbnail.processor';
|
||||
import { UserDeletionProcessor } from './processors/user-deletion.processor';
|
||||
import { VideoTranscodeProcessor } from './processors/video-transcode.processor';
|
||||
import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.constant';
|
||||
import { DomainModule } from '@app/domain';
|
||||
|
||||
@Module({
|
||||
|
@ -25,11 +22,8 @@ import { DomainModule } from '@app/domain';
|
|||
DomainModule.register({
|
||||
imports: [InfraModule],
|
||||
}),
|
||||
ImmichConfigModule,
|
||||
TypeOrmModule.forFeature([UserEntity, ExifEntity, AssetEntity, SmartInfoEntity, APIKeyEntity]),
|
||||
StorageModule,
|
||||
BullModule.forRootAsync(immichBullAsyncConfig),
|
||||
BullModule.registerQueue(...immichSharedQueues),
|
||||
CommunicationModule,
|
||||
],
|
||||
controllers: [],
|
||||
|
@ -44,6 +38,5 @@ import { DomainModule } from '@app/domain';
|
|||
UserDeletionProcessor,
|
||||
StorageMigrationProcessor,
|
||||
],
|
||||
exports: [BullModule],
|
||||
})
|
||||
export class MicroservicesModule {}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { QueueName } from '@app/job';
|
||||
import { QueueName } from '@app/domain';
|
||||
import { InjectQueue } from '@nestjs/bull';
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { Queue } from 'bull';
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
IVideoTranscodeJob,
|
||||
QueueName,
|
||||
JobName,
|
||||
} from '@app/job';
|
||||
} from '@app/domain';
|
||||
import { InjectQueue, Process, Processor } from '@nestjs/bull';
|
||||
import { Job, Queue } from 'bull';
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { AssetEntity } from '@app/infra';
|
||||
import { QueueName } from '@app/job';
|
||||
import { QueueName } from '@app/domain';
|
||||
import { Process, Processor } from '@nestjs/bull';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AssetEntity } from '@app/infra';
|
||||
import { SmartInfoEntity } from '@app/infra';
|
||||
import { QueueName, JobName } from '@app/job';
|
||||
import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface';
|
||||
import { QueueName, JobName } from '@app/domain';
|
||||
import { IMachineLearningJob } from '@app/domain';
|
||||
import { Process, Processor } from '@nestjs/bull';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
IVideoLengthExtractionProcessor,
|
||||
QueueName,
|
||||
JobName,
|
||||
} from '@app/job';
|
||||
} from '@app/domain';
|
||||
import { Process, Processor } from '@nestjs/bull';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { APP_UPLOAD_LOCATION } from '@app/common';
|
||||
import { AssetEntity } from '@app/infra';
|
||||
import { ImmichConfigService } from '@app/immich-config';
|
||||
import { QueueName, JobName } from '@app/job';
|
||||
import { SystemConfigService } from '@app/domain';
|
||||
import { QueueName, JobName } from '@app/domain';
|
||||
import { StorageService } from '@app/storage';
|
||||
import { Process, Processor } from '@nestjs/bull';
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
@ -14,7 +14,7 @@ export class StorageMigrationProcessor {
|
|||
|
||||
constructor(
|
||||
private storageService: StorageService,
|
||||
private immichConfigService: ImmichConfigService,
|
||||
private systemConfigService: SystemConfigService,
|
||||
|
||||
@InjectRepository(AssetEntity)
|
||||
private assetRepository: Repository<AssetEntity>,
|
||||
|
@ -56,6 +56,6 @@ export class StorageMigrationProcessor {
|
|||
*/
|
||||
@Process({ name: JobName.CONFIG_CHANGE, concurrency: 1 })
|
||||
async updateTemplate() {
|
||||
await this.immichConfigService.refreshConfig();
|
||||
await this.systemConfigService.refreshConfig();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { APP_UPLOAD_LOCATION } from '@app/common';
|
||||
import { AssetEntity, AssetType } from '@app/infra';
|
||||
import { WebpGeneratorProcessor, JpegGeneratorProcessor, QueueName, JobName } from '@app/job';
|
||||
import { WebpGeneratorProcessor, JpegGeneratorProcessor, QueueName, JobName } from '@app/domain';
|
||||
import { InjectQueue, Process, Processor } from '@nestjs/bull';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
@ -13,7 +13,7 @@ import sharp from 'sharp';
|
|||
import { Repository } from 'typeorm/repository/Repository';
|
||||
import { join } from 'path';
|
||||
import { CommunicationGateway } from 'apps/immich/src/api-v1/communication/communication.gateway';
|
||||
import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface';
|
||||
import { IMachineLearningJob } from '@app/domain';
|
||||
|
||||
@Processor(QueueName.THUMBNAIL_GENERATION)
|
||||
export class ThumbnailGeneratorProcessor {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { APP_UPLOAD_LOCATION, userUtils } from '@app/common';
|
||||
import { APIKeyEntity, AssetEntity, UserEntity } from '@app/infra';
|
||||
import { QueueName, JobName } from '@app/job';
|
||||
import { IUserDeletionJob } from '@app/job/interfaces/user-deletion.interface';
|
||||
import { QueueName, JobName } from '@app/domain';
|
||||
import { IUserDeletionJob } from '@app/domain';
|
||||
import { Process, Processor } from '@nestjs/bull';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { APP_UPLOAD_LOCATION } from '@app/common/constants';
|
||||
import { AssetEntity } from '@app/infra';
|
||||
import { QueueName, JobName } from '@app/job';
|
||||
import { IMp4ConversionProcessor } from '@app/job/interfaces/video-transcode.interface';
|
||||
import { QueueName, JobName } from '@app/domain';
|
||||
import { IMp4ConversionProcessor } from '@app/domain';
|
||||
import { Process, Processor } from '@nestjs/bull';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Job } from 'bull';
|
||||
import ffmpeg from 'fluent-ffmpeg';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { ImmichConfigService } from 'libs/immich-config/src';
|
||||
import { SystemConfigService } from '@app/domain';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@Processor(QueueName.VIDEO_CONVERSION)
|
||||
|
@ -16,7 +16,7 @@ export class VideoTranscodeProcessor {
|
|||
constructor(
|
||||
@InjectRepository(AssetEntity)
|
||||
private assetRepository: Repository<AssetEntity>,
|
||||
private immichConfigService: ImmichConfigService,
|
||||
private systemConfigService: SystemConfigService,
|
||||
) {}
|
||||
|
||||
@Process({ name: JobName.MP4_CONVERSION, concurrency: 2 })
|
||||
|
@ -41,7 +41,7 @@ export class VideoTranscodeProcessor {
|
|||
}
|
||||
|
||||
async runFFMPEGPipeLine(asset: AssetEntity, savedEncodedPath: string): Promise<void> {
|
||||
const config = await this.immichConfigService.getConfig();
|
||||
const config = await this.systemConfigService.getConfig();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ffmpeg(asset.originalPath)
|
||||
|
|
|
@ -148,6 +148,122 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/system-config": {
|
||||
"get": {
|
||||
"operationId": "getConfig",
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemConfigDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"System Config"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"operationId": "updateConfig",
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemConfigDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemConfigDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"System Config"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/system-config/defaults": {
|
||||
"get": {
|
||||
"operationId": "getDefaults",
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemConfigDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"System Config"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/system-config/storage-template-options": {
|
||||
"get": {
|
||||
"operationId": "getStorageTemplateOptions",
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemConfigTemplateStorageOptionDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"System Config"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/user": {
|
||||
"get": {
|
||||
"operationId": "getAllUsers",
|
||||
|
@ -2683,122 +2799,6 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/system-config": {
|
||||
"get": {
|
||||
"operationId": "getConfig",
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemConfigDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"System Config"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"operationId": "updateConfig",
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemConfigDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemConfigDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"System Config"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/system-config/defaults": {
|
||||
"get": {
|
||||
"operationId": "getDefaults",
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemConfigDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"System Config"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/system-config/storage-template-options": {
|
||||
"get": {
|
||||
"operationId": "getStorageTemplateOptions",
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemConfigTemplateStorageOptionDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"System Config"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
|
@ -2882,6 +2882,181 @@
|
|||
"name"
|
||||
]
|
||||
},
|
||||
"SystemConfigFFmpegDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"crf": {
|
||||
"type": "string"
|
||||
},
|
||||
"preset": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetVideoCodec": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetAudioCodec": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetScaling": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"crf",
|
||||
"preset",
|
||||
"targetVideoCodec",
|
||||
"targetAudioCodec",
|
||||
"targetScaling"
|
||||
]
|
||||
},
|
||||
"SystemConfigOAuthDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"issuerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientSecret": {
|
||||
"type": "string"
|
||||
},
|
||||
"scope": {
|
||||
"type": "string"
|
||||
},
|
||||
"buttonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"autoRegister": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"autoLaunch": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mobileOverrideEnabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mobileRedirectUri": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled",
|
||||
"issuerUrl",
|
||||
"clientId",
|
||||
"clientSecret",
|
||||
"scope",
|
||||
"buttonText",
|
||||
"autoRegister",
|
||||
"autoLaunch",
|
||||
"mobileOverrideEnabled",
|
||||
"mobileRedirectUri"
|
||||
]
|
||||
},
|
||||
"SystemConfigPasswordLoginDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled"
|
||||
]
|
||||
},
|
||||
"SystemConfigStorageTemplateDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"template": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"template"
|
||||
]
|
||||
},
|
||||
"SystemConfigDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ffmpeg": {
|
||||
"$ref": "#/components/schemas/SystemConfigFFmpegDto"
|
||||
},
|
||||
"oauth": {
|
||||
"$ref": "#/components/schemas/SystemConfigOAuthDto"
|
||||
},
|
||||
"passwordLogin": {
|
||||
"$ref": "#/components/schemas/SystemConfigPasswordLoginDto"
|
||||
},
|
||||
"storageTemplate": {
|
||||
"$ref": "#/components/schemas/SystemConfigStorageTemplateDto"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"ffmpeg",
|
||||
"oauth",
|
||||
"passwordLogin",
|
||||
"storageTemplate"
|
||||
]
|
||||
},
|
||||
"SystemConfigTemplateStorageOptionDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"yearOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"monthOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"dayOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"hourOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"minuteOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"secondOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"presetOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"yearOptions",
|
||||
"monthOptions",
|
||||
"dayOptions",
|
||||
"hourOptions",
|
||||
"minuteOptions",
|
||||
"secondOptions",
|
||||
"presetOptions"
|
||||
]
|
||||
},
|
||||
"UserResponseDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -4476,181 +4651,6 @@
|
|||
"required": [
|
||||
"command"
|
||||
]
|
||||
},
|
||||
"SystemConfigFFmpegDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"crf": {
|
||||
"type": "string"
|
||||
},
|
||||
"preset": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetVideoCodec": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetAudioCodec": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetScaling": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"crf",
|
||||
"preset",
|
||||
"targetVideoCodec",
|
||||
"targetAudioCodec",
|
||||
"targetScaling"
|
||||
]
|
||||
},
|
||||
"SystemConfigOAuthDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"issuerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientSecret": {
|
||||
"type": "string"
|
||||
},
|
||||
"scope": {
|
||||
"type": "string"
|
||||
},
|
||||
"buttonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"autoRegister": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"autoLaunch": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mobileOverrideEnabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mobileRedirectUri": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled",
|
||||
"issuerUrl",
|
||||
"clientId",
|
||||
"clientSecret",
|
||||
"scope",
|
||||
"buttonText",
|
||||
"autoRegister",
|
||||
"autoLaunch",
|
||||
"mobileOverrideEnabled",
|
||||
"mobileRedirectUri"
|
||||
]
|
||||
},
|
||||
"SystemConfigPasswordLoginDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled"
|
||||
]
|
||||
},
|
||||
"SystemConfigStorageTemplateDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"template": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"template"
|
||||
]
|
||||
},
|
||||
"SystemConfigDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ffmpeg": {
|
||||
"$ref": "#/components/schemas/SystemConfigFFmpegDto"
|
||||
},
|
||||
"oauth": {
|
||||
"$ref": "#/components/schemas/SystemConfigOAuthDto"
|
||||
},
|
||||
"passwordLogin": {
|
||||
"$ref": "#/components/schemas/SystemConfigPasswordLoginDto"
|
||||
},
|
||||
"storageTemplate": {
|
||||
"$ref": "#/components/schemas/SystemConfigStorageTemplateDto"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"ffmpeg",
|
||||
"oauth",
|
||||
"passwordLogin",
|
||||
"storageTemplate"
|
||||
]
|
||||
},
|
||||
"SystemConfigTemplateStorageOptionDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"yearOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"monthOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"dayOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"hourOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"minuteOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"secondOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"presetOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"yearOptions",
|
||||
"monthOptions",
|
||||
"dayOptions",
|
||||
"hourOptions",
|
||||
"minuteOptions",
|
||||
"secondOptions",
|
||||
"presetOptions"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import { SharedBullAsyncConfiguration } from '@nestjs/bull';
|
||||
|
||||
export const immichBullAsyncConfig: SharedBullAsyncConfiguration = {
|
||||
useFactory: async () => ({
|
||||
prefix: 'immich_bull',
|
||||
redis: {
|
||||
host: process.env.REDIS_HOSTNAME || 'immich_redis',
|
||||
port: parseInt(process.env.REDIS_PORT || '6379'),
|
||||
db: parseInt(process.env.REDIS_DBINDEX || '0'),
|
||||
password: process.env.REDIS_PASSWORD || undefined,
|
||||
path: process.env.REDIS_SOCKET || undefined,
|
||||
},
|
||||
defaultJobOptions: {
|
||||
attempts: 3,
|
||||
removeOnComplete: true,
|
||||
removeOnFail: false,
|
||||
},
|
||||
}),
|
||||
};
|
|
@ -1,2 +1 @@
|
|||
export * from './app.config';
|
||||
export * from './bull-queue.config';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { APIKeyEntity } from '@app/infra';
|
||||
import { APIKeyEntity } from '@app/infra/db/entities';
|
||||
|
||||
export const IKeyRepository = 'IKeyRepository';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { APIKeyEntity } from '@app/infra';
|
||||
import { APIKeyEntity } from '@app/infra/db/entities';
|
||||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import { authStub, entityStub, newCryptoRepositoryMock, newKeyRepositoryMock } from '../../test';
|
||||
import { ICryptoRepository } from '../auth';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { UserEntity } from '@app/infra';
|
||||
import { UserEntity } from '@app/infra/db/entities';
|
||||
import { BadRequestException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthUserDto, ICryptoRepository } from '../auth';
|
||||
import { IKeyRepository } from './api-key.repository';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { APIKeyEntity } from '@app/infra';
|
||||
import { APIKeyEntity } from '@app/infra/db/entities';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class APIKeyResponseDto {
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common';
|
||||
import { APIKeyService } from './api-key';
|
||||
import { SystemConfigService } from './system-config';
|
||||
import { UserService } from './user';
|
||||
|
||||
export const INITIAL_SYSTEM_CONFIG = 'INITIAL_SYSTEM_CONFIG';
|
||||
|
||||
const providers: Provider[] = [
|
||||
//
|
||||
APIKeyService,
|
||||
SystemConfigService,
|
||||
UserService,
|
||||
|
||||
{
|
||||
provide: INITIAL_SYSTEM_CONFIG,
|
||||
inject: [SystemConfigService],
|
||||
useFactory: async (configService: SystemConfigService) => {
|
||||
return configService.getConfig();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@Global()
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export * from './api-key';
|
||||
export * from './auth';
|
||||
export * from './domain.module';
|
||||
export * from './job';
|
||||
export * from './system-config';
|
||||
export * from './user';
|
||||
|
|
3
server/libs/domain/src/job/index.ts
Normal file
3
server/libs/domain/src/job/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from './interfaces';
|
||||
export * from './job.constants';
|
||||
export * from './job.repository';
|
|
@ -1,4 +1,4 @@
|
|||
import { AssetEntity } from '@app/infra';
|
||||
import { AssetEntity } from '@app/infra/db/entities';
|
||||
|
||||
export interface IAssetUploadedJob {
|
||||
/**
|
|
@ -0,0 +1,5 @@
|
|||
import { AssetEntity } from '@app/infra/db/entities';
|
||||
|
||||
export interface IDeleteFileOnDiskJob {
|
||||
assets: AssetEntity[];
|
||||
}
|
7
server/libs/domain/src/job/interfaces/index.ts
Normal file
7
server/libs/domain/src/job/interfaces/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export * from './asset-uploaded.interface';
|
||||
export * from './background-task.interface';
|
||||
export * from './machine-learning.interface';
|
||||
export * from './metadata-extraction.interface';
|
||||
export * from './thumbnail-generation.interface';
|
||||
export * from './user-deletion.interface';
|
||||
export * from './video-transcode.interface';
|
|
@ -1,4 +1,4 @@
|
|||
import { AssetEntity } from '@app/infra';
|
||||
import { AssetEntity } from '@app/infra/db/entities';
|
||||
|
||||
export interface IMachineLearningJob {
|
||||
/**
|
|
@ -1,4 +1,4 @@
|
|||
import { AssetEntity } from '@app/infra';
|
||||
import { AssetEntity } from '@app/infra/db/entities';
|
||||
|
||||
export interface IExifExtractionProcessor {
|
||||
/**
|
|
@ -1,4 +1,4 @@
|
|||
import { AssetEntity } from '@app/infra';
|
||||
import { AssetEntity } from '@app/infra/db/entities';
|
||||
|
||||
export interface JpegGeneratorProcessor {
|
||||
/**
|
|
@ -1,4 +1,4 @@
|
|||
import { UserEntity } from '@app/infra';
|
||||
import { UserEntity } from '@app/infra/db/entities';
|
||||
|
||||
export interface IUserDeletionJob {
|
||||
/**
|
|
@ -1,4 +1,4 @@
|
|||
import { AssetEntity } from '@app/infra';
|
||||
import { AssetEntity } from '@app/infra/db/entities';
|
||||
|
||||
export interface IMp4ConversionProcessor {
|
||||
/**
|
|
@ -1,3 +1,15 @@
|
|||
export enum QueueName {
|
||||
THUMBNAIL_GENERATION = 'thumbnail-generation-queue',
|
||||
METADATA_EXTRACTION = 'metadata-extraction-queue',
|
||||
VIDEO_CONVERSION = 'video-conversion-queue',
|
||||
CHECKSUM_GENERATION = 'generate-checksum-queue',
|
||||
ASSET_UPLOADED = 'asset-uploaded-queue',
|
||||
MACHINE_LEARNING = 'machine-learning-queue',
|
||||
USER_DELETION = 'user-deletion-queue',
|
||||
CONFIG = 'config-queue',
|
||||
BACKGROUND_TASK = 'background-task',
|
||||
}
|
||||
|
||||
export enum JobName {
|
||||
ASSET_UPLOADED = 'asset-uploaded',
|
||||
MP4_CONVERSION = 'mp4-conversion',
|
32
server/libs/domain/src/job/job.repository.ts
Normal file
32
server/libs/domain/src/job/job.repository.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import {
|
||||
IAssetUploadedJob,
|
||||
IDeleteFileOnDiskJob,
|
||||
IExifExtractionProcessor,
|
||||
IMachineLearningJob,
|
||||
IMp4ConversionProcessor,
|
||||
IReverseGeocodingProcessor,
|
||||
IUserDeletionJob,
|
||||
JpegGeneratorProcessor,
|
||||
WebpGeneratorProcessor,
|
||||
} from './interfaces';
|
||||
import { JobName } from './job.constants';
|
||||
|
||||
export type JobItem =
|
||||
| { name: JobName.ASSET_UPLOADED; data: IAssetUploadedJob }
|
||||
| { name: JobName.MP4_CONVERSION; data: IMp4ConversionProcessor }
|
||||
| { name: JobName.GENERATE_JPEG_THUMBNAIL; data: JpegGeneratorProcessor }
|
||||
| { name: JobName.GENERATE_WEBP_THUMBNAIL; data: WebpGeneratorProcessor }
|
||||
| { name: JobName.EXIF_EXTRACTION; data: IExifExtractionProcessor }
|
||||
| { name: JobName.REVERSE_GEOCODING; data: IReverseGeocodingProcessor }
|
||||
| { name: JobName.USER_DELETION; data: IUserDeletionJob }
|
||||
| { name: JobName.TEMPLATE_MIGRATION }
|
||||
| { name: JobName.CONFIG_CHANGE }
|
||||
| { name: JobName.OBJECT_DETECTION; data: IMachineLearningJob }
|
||||
| { name: JobName.IMAGE_TAGGING; data: IMachineLearningJob }
|
||||
| { name: JobName.DELETE_FILE_ON_DISK; data: IDeleteFileOnDiskJob };
|
||||
|
||||
export const IJobRepository = 'IJobRepository';
|
||||
|
||||
export interface IJobRepository {
|
||||
add(item: JobItem): Promise<void>;
|
||||
}
|
5
server/libs/domain/src/system-config/dto/index.ts
Normal file
5
server/libs/domain/src/system-config/dto/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export * from './system-config-ffmpeg.dto';
|
||||
export * from './system-config-oauth.dto';
|
||||
export * from './system-config-password-login.dto';
|
||||
export * from './system-config-storage-template.dto';
|
||||
export * from './system-config.dto';
|
|
@ -1,4 +1,4 @@
|
|||
import { SystemConfig } from '@app/infra';
|
||||
import { SystemConfig } from '@app/infra/db/entities';
|
||||
import { ValidateNested } from 'class-validator';
|
||||
import { SystemConfigFFmpegDto } from './system-config-ffmpeg.dto';
|
||||
import { SystemConfigOAuthDto } from './system-config-oauth.dto';
|
5
server/libs/domain/src/system-config/index.ts
Normal file
5
server/libs/domain/src/system-config/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export * from './dto';
|
||||
export * from './response-dto';
|
||||
export * from './system-config.repository';
|
||||
export * from './system-config.service';
|
||||
export * from './system-config.datetime-variables';
|
|
@ -0,0 +1 @@
|
|||
export * from './system-config-template-storage-option.dto';
|
|
@ -1,9 +1,9 @@
|
|||
import { SystemConfig, SystemConfigEntity, SystemConfigKey } from '@app/infra';
|
||||
import { SystemConfig, SystemConfigEntity, SystemConfigKey } from '@app/infra/db/entities';
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import * as _ from 'lodash';
|
||||
import { Subject } from 'rxjs';
|
||||
import { DeepPartial, In, Repository } from 'typeorm';
|
||||
import { DeepPartial } from 'typeorm';
|
||||
import { ISystemConfigRepository } from './system-config.repository';
|
||||
|
||||
export type SystemConfigValidator = (config: SystemConfig) => void | Promise<void>;
|
||||
|
||||
|
@ -37,16 +37,13 @@ const defaults: SystemConfig = Object.freeze({
|
|||
});
|
||||
|
||||
@Injectable()
|
||||
export class ImmichConfigService {
|
||||
private logger = new Logger(ImmichConfigService.name);
|
||||
export class SystemConfigCore {
|
||||
private logger = new Logger(SystemConfigCore.name);
|
||||
private validators: SystemConfigValidator[] = [];
|
||||
|
||||
public config$ = new Subject<SystemConfig>();
|
||||
|
||||
constructor(
|
||||
@InjectRepository(SystemConfigEntity)
|
||||
private systemConfigRepository: Repository<SystemConfigEntity>,
|
||||
) {}
|
||||
constructor(private repository: ISystemConfigRepository) {}
|
||||
|
||||
public getDefaults(): SystemConfig {
|
||||
return defaults;
|
||||
|
@ -57,7 +54,7 @@ export class ImmichConfigService {
|
|||
}
|
||||
|
||||
public async getConfig() {
|
||||
const overrides = await this.systemConfigRepository.find();
|
||||
const overrides = await this.repository.load();
|
||||
const config: DeepPartial<SystemConfig> = {};
|
||||
for (const { key, value } of overrides) {
|
||||
// set via dot notation
|
||||
|
@ -95,11 +92,11 @@ export class ImmichConfigService {
|
|||
}
|
||||
|
||||
if (updates.length > 0) {
|
||||
await this.systemConfigRepository.save(updates);
|
||||
await this.repository.saveAll(updates);
|
||||
}
|
||||
|
||||
if (deletes.length > 0) {
|
||||
await this.systemConfigRepository.delete({ key: In(deletes.map((item) => item.key)) });
|
||||
await this.repository.deleteKeys(deletes.map((item) => item.key));
|
||||
}
|
||||
|
||||
const newConfig = await this.getConfig();
|
|
@ -0,0 +1,9 @@
|
|||
import { SystemConfigEntity } from '@app/infra/db/entities';
|
||||
|
||||
export const ISystemConfigRepository = 'ISystemConfigRepository';
|
||||
|
||||
export interface ISystemConfigRepository {
|
||||
load(): Promise<SystemConfigEntity[]>;
|
||||
saveAll(items: SystemConfigEntity[]): Promise<SystemConfigEntity[]>;
|
||||
deleteKeys(keys: string[]): Promise<void>;
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
import { SystemConfigEntity, SystemConfigKey } from '@app/infra';
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { newJobRepositoryMock, newSystemConfigRepositoryMock, systemConfigStub } from '../../test';
|
||||
import { IJobRepository, JobName } from '../job';
|
||||
import { SystemConfigValidator } from './system-config.core';
|
||||
import { ISystemConfigRepository } from './system-config.repository';
|
||||
import { SystemConfigService } from './system-config.service';
|
||||
|
||||
const updates: SystemConfigEntity[] = [
|
||||
{ key: SystemConfigKey.FFMPEG_CRF, value: 'a new value' },
|
||||
{ key: SystemConfigKey.OAUTH_AUTO_LAUNCH, value: true },
|
||||
];
|
||||
|
||||
const updatedConfig = Object.freeze({
|
||||
ffmpeg: {
|
||||
crf: 'a new value',
|
||||
preset: 'ultrafast',
|
||||
targetAudioCodec: 'mp3',
|
||||
targetScaling: '1280:-2',
|
||||
targetVideoCodec: 'libx264',
|
||||
},
|
||||
oauth: {
|
||||
autoLaunch: true,
|
||||
autoRegister: true,
|
||||
buttonText: 'Login with OAuth',
|
||||
clientId: '',
|
||||
clientSecret: '',
|
||||
enabled: false,
|
||||
issuerUrl: '',
|
||||
mobileOverrideEnabled: false,
|
||||
mobileRedirectUri: '',
|
||||
scope: 'openid email profile',
|
||||
},
|
||||
passwordLogin: {
|
||||
enabled: true,
|
||||
},
|
||||
storageTemplate: {
|
||||
template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
|
||||
},
|
||||
});
|
||||
|
||||
describe(SystemConfigService.name, () => {
|
||||
let sut: SystemConfigService;
|
||||
let configMock: jest.Mocked<ISystemConfigRepository>;
|
||||
let jobMock: jest.Mocked<IJobRepository>;
|
||||
|
||||
beforeEach(async () => {
|
||||
configMock = newSystemConfigRepositoryMock();
|
||||
jobMock = newJobRepositoryMock();
|
||||
sut = new SystemConfigService(configMock, jobMock);
|
||||
});
|
||||
|
||||
it('should work', () => {
|
||||
expect(sut).toBeDefined();
|
||||
});
|
||||
|
||||
describe('getDefaults', () => {
|
||||
it('should return the default config', () => {
|
||||
configMock.load.mockResolvedValue(updates);
|
||||
|
||||
expect(sut.getDefaults()).toEqual(systemConfigStub.defaults);
|
||||
expect(configMock.load).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('addValidator', () => {
|
||||
it('should call the validator on config changes', async () => {
|
||||
const validator: SystemConfigValidator = jest.fn();
|
||||
|
||||
sut.addValidator(validator);
|
||||
|
||||
await sut.updateConfig(systemConfigStub.defaults);
|
||||
|
||||
expect(validator).toHaveBeenCalledWith(systemConfigStub.defaults);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConfig', () => {
|
||||
it('should return the default config', async () => {
|
||||
configMock.load.mockResolvedValue([]);
|
||||
|
||||
await expect(sut.getConfig()).resolves.toEqual(systemConfigStub.defaults);
|
||||
});
|
||||
|
||||
it('should merge the overrides', async () => {
|
||||
configMock.load.mockResolvedValue([
|
||||
{ key: SystemConfigKey.FFMPEG_CRF, value: 'a new value' },
|
||||
{ key: SystemConfigKey.OAUTH_AUTO_LAUNCH, value: true },
|
||||
]);
|
||||
|
||||
await expect(sut.getConfig()).resolves.toEqual(updatedConfig);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStorageTemplateOptions', () => {
|
||||
it('should send back the datetime variables', () => {
|
||||
expect(sut.getStorageTemplateOptions()).toEqual({
|
||||
dayOptions: ['d', 'dd'],
|
||||
hourOptions: ['h', 'hh', 'H', 'HH'],
|
||||
minuteOptions: ['m', 'mm'],
|
||||
monthOptions: ['M', 'MM', 'MMM', 'MMMM'],
|
||||
presetOptions: [
|
||||
'{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
|
||||
'{{y}}/{{MM}}-{{dd}}/{{filename}}',
|
||||
'{{y}}/{{MMMM}}-{{dd}}/{{filename}}',
|
||||
'{{y}}/{{MM}}/{{filename}}',
|
||||
'{{y}}/{{MMM}}/{{filename}}',
|
||||
'{{y}}/{{MMMM}}/{{filename}}',
|
||||
'{{y}}/{{MM}}/{{dd}}/{{filename}}',
|
||||
'{{y}}/{{MMMM}}/{{dd}}/{{filename}}',
|
||||
'{{y}}/{{y}}-{{MM}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
|
||||
'{{y}}-{{MM}}-{{dd}}/{{filename}}',
|
||||
'{{y}}-{{MMM}}-{{dd}}/{{filename}}',
|
||||
'{{y}}-{{MMMM}}-{{dd}}/{{filename}}',
|
||||
],
|
||||
secondOptions: ['s', 'ss'],
|
||||
yearOptions: ['y', 'yy'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateConfig', () => {
|
||||
it('should notify the microservices process', async () => {
|
||||
configMock.load.mockResolvedValue(updates);
|
||||
|
||||
await expect(sut.updateConfig(updatedConfig)).resolves.toEqual(updatedConfig);
|
||||
|
||||
expect(configMock.saveAll).toHaveBeenCalledWith(updates);
|
||||
expect(jobMock.add).toHaveBeenCalledWith({ name: JobName.CONFIG_CHANGE });
|
||||
});
|
||||
|
||||
it('should throw an error if the config is not valid', async () => {
|
||||
const validator = jest.fn().mockRejectedValue('invalid config');
|
||||
|
||||
sut.addValidator(validator);
|
||||
|
||||
await expect(sut.updateConfig(updatedConfig)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(validator).toHaveBeenCalledWith(updatedConfig);
|
||||
expect(configMock.saveAll).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshConfig', () => {
|
||||
it('should notify the subscribers', async () => {
|
||||
const changeMock = jest.fn();
|
||||
const subscription = sut.config$.subscribe(changeMock);
|
||||
|
||||
await sut.refreshConfig();
|
||||
|
||||
expect(changeMock).toHaveBeenCalledWith(systemConfigStub.defaults);
|
||||
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,68 @@
|
|||
import { ISystemConfigRepository } from '../system-config';
|
||||
import {
|
||||
supportedDayTokens,
|
||||
supportedHourTokens,
|
||||
supportedMinuteTokens,
|
||||
supportedMonthTokens,
|
||||
supportedPresetTokens,
|
||||
supportedSecondTokens,
|
||||
supportedYearTokens,
|
||||
} from './system-config.datetime-variables';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { IJobRepository, JobName } from '../job';
|
||||
import { mapConfig, SystemConfigDto } from './dto/system-config.dto';
|
||||
import { SystemConfigTemplateStorageOptionDto } from './response-dto/system-config-template-storage-option.dto';
|
||||
import { SystemConfigCore, SystemConfigValidator } from './system-config.core';
|
||||
|
||||
@Injectable()
|
||||
export class SystemConfigService {
|
||||
private core: SystemConfigCore;
|
||||
constructor(
|
||||
@Inject(ISystemConfigRepository) repository: ISystemConfigRepository,
|
||||
@Inject(IJobRepository) private queue: IJobRepository,
|
||||
) {
|
||||
this.core = new SystemConfigCore(repository);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async updateConfig(dto: SystemConfigDto): Promise<SystemConfigDto> {
|
||||
const config = await this.core.updateConfig(dto);
|
||||
await this.queue.add({ name: JobName.CONFIG_CHANGE });
|
||||
return mapConfig(config);
|
||||
}
|
||||
|
||||
async refreshConfig() {
|
||||
await this.core.refreshConfig();
|
||||
}
|
||||
|
||||
addValidator(validator: SystemConfigValidator) {
|
||||
this.core.addValidator(validator);
|
||||
}
|
||||
|
||||
getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
|
||||
const options = new SystemConfigTemplateStorageOptionDto();
|
||||
|
||||
options.dayOptions = supportedDayTokens;
|
||||
options.monthOptions = supportedMonthTokens;
|
||||
options.yearOptions = supportedYearTokens;
|
||||
options.hourOptions = supportedHourTokens;
|
||||
options.secondOptions = supportedSecondTokens;
|
||||
options.minuteOptions = supportedMinuteTokens;
|
||||
options.presetOptions = supportedPresetTokens;
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { UserEntity } from '@app/infra';
|
||||
import { UserEntity } from '@app/infra/db/entities';
|
||||
|
||||
export class UserResponseDto {
|
||||
id!: string;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { UserEntity } from '@app/infra';
|
||||
import { UserEntity } from '@app/infra/db/entities';
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { UserEntity } from '@app/infra';
|
||||
import { UserEntity } from '@app/infra/db/entities';
|
||||
|
||||
export interface UserListFilter {
|
||||
excludeId?: string;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { IUserRepository } from '@app/domain';
|
||||
import { UserEntity } from '@app/infra';
|
||||
import { IUserRepository } from './user.repository';
|
||||
import { UserEntity } from '@app/infra/db/entities';
|
||||
import { BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common';
|
||||
import { when } from 'jest-when';
|
||||
import { newUserRepositoryMock } from '../../test';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { UserEntity } from '@app/infra';
|
||||
import { SystemConfig, UserEntity } from '@app/infra/db/entities';
|
||||
import { AuthUserDto } from '../src';
|
||||
|
||||
export const authStub = {
|
||||
|
@ -42,3 +42,33 @@ export const entityStub = {
|
|||
tags: [],
|
||||
}),
|
||||
};
|
||||
|
||||
export const systemConfigStub = {
|
||||
defaults: Object.freeze({
|
||||
ffmpeg: {
|
||||
crf: '23',
|
||||
preset: 'ultrafast',
|
||||
targetAudioCodec: 'mp3',
|
||||
targetScaling: '1280:-2',
|
||||
targetVideoCodec: 'libx264',
|
||||
},
|
||||
oauth: {
|
||||
autoLaunch: false,
|
||||
autoRegister: true,
|
||||
buttonText: 'Login with OAuth',
|
||||
clientId: '',
|
||||
clientSecret: '',
|
||||
enabled: false,
|
||||
issuerUrl: '',
|
||||
mobileOverrideEnabled: false,
|
||||
mobileRedirectUri: '',
|
||||
scope: 'openid email profile',
|
||||
},
|
||||
passwordLogin: {
|
||||
enabled: true,
|
||||
},
|
||||
storageTemplate: {
|
||||
template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
|
||||
},
|
||||
} as SystemConfig),
|
||||
};
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export * from './api-key.repository.mock';
|
||||
export * from './crypto.repository.mock';
|
||||
export * from './fixtures';
|
||||
export * from './job.repository.mock';
|
||||
export * from './system-config.repository.mock';
|
||||
export * from './user.repository.mock';
|
||||
|
|
7
server/libs/domain/test/job.repository.mock.ts
Normal file
7
server/libs/domain/test/job.repository.mock.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { IJobRepository } from '../src';
|
||||
|
||||
export const newJobRepositoryMock = (): jest.Mocked<IJobRepository> => {
|
||||
return {
|
||||
add: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
};
|
||||
};
|
9
server/libs/domain/test/system-config.repository.mock.ts
Normal file
9
server/libs/domain/test/system-config.repository.mock.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { ISystemConfigRepository } from '../src';
|
||||
|
||||
export const newSystemConfigRepositoryMock = (): jest.Mocked<ISystemConfigRepository> => {
|
||||
return {
|
||||
load: jest.fn().mockResolvedValue([]),
|
||||
saveAll: jest.fn().mockResolvedValue([]),
|
||||
deleteKeys: jest.fn(),
|
||||
};
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
import { SystemConfigEntity } from '@app/infra';
|
||||
import { Module, Provider } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ImmichConfigService } from './immich-config.service';
|
||||
|
||||
export const INITIAL_SYSTEM_CONFIG = 'INITIAL_SYSTEM_CONFIG';
|
||||
|
||||
const providers: Provider[] = [
|
||||
ImmichConfigService,
|
||||
{
|
||||
provide: INITIAL_SYSTEM_CONFIG,
|
||||
inject: [ImmichConfigService],
|
||||
useFactory: async (configService: ImmichConfigService) => {
|
||||
return configService.getConfig();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([SystemConfigEntity])],
|
||||
providers: [...providers],
|
||||
exports: [...providers],
|
||||
})
|
||||
export class ImmichConfigModule {}
|
|
@ -1,2 +0,0 @@
|
|||
export * from './immich-config.module';
|
||||
export * from './immich-config.service';
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"outDir": "../../dist/libs/immich-config"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { ISystemConfigRepository } from '@app/domain';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { SystemConfigEntity } from '../entities';
|
||||
|
||||
export class SystemConfigRepository implements ISystemConfigRepository {
|
||||
constructor(
|
||||
@InjectRepository(SystemConfigEntity)
|
||||
private repository: Repository<SystemConfigEntity>,
|
||||
) {}
|
||||
|
||||
load(): Promise<SystemConfigEntity<string | boolean>[]> {
|
||||
return this.repository.find();
|
||||
}
|
||||
|
||||
saveAll(items: SystemConfigEntity[]): Promise<SystemConfigEntity[]> {
|
||||
return this.repository.save(items);
|
||||
}
|
||||
|
||||
async deleteKeys(keys: string[]): Promise<void> {
|
||||
await this.repository.delete({ key: In(keys) });
|
||||
}
|
||||
}
|
|
@ -1,26 +1,65 @@
|
|||
import { ICryptoRepository, IKeyRepository, IUserRepository } from '@app/domain';
|
||||
import {
|
||||
ICryptoRepository,
|
||||
IJobRepository,
|
||||
IKeyRepository,
|
||||
ISystemConfigRepository,
|
||||
IUserRepository,
|
||||
QueueName,
|
||||
} from '@app/domain';
|
||||
import { databaseConfig, UserEntity } from '@app/infra';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { Global, Module, Provider } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { cryptoRepository } from './auth/crypto.repository';
|
||||
import { APIKeyEntity, UserRepository } from './db';
|
||||
import { APIKeyEntity, SystemConfigEntity, UserRepository } from './db';
|
||||
import { APIKeyRepository } from './db/repository';
|
||||
import { SystemConfigRepository } from './db/repository/system-config.repository';
|
||||
import { JobRepository } from './job';
|
||||
|
||||
const providers: Provider[] = [
|
||||
//
|
||||
{ provide: ICryptoRepository, useValue: cryptoRepository },
|
||||
{ provide: IKeyRepository, useClass: APIKeyRepository },
|
||||
{ provide: IJobRepository, useClass: JobRepository },
|
||||
{ provide: ISystemConfigRepository, useClass: SystemConfigRepository },
|
||||
{ provide: IUserRepository, useClass: UserRepository },
|
||||
];
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
//
|
||||
TypeOrmModule.forRoot(databaseConfig),
|
||||
TypeOrmModule.forFeature([APIKeyEntity, UserEntity]),
|
||||
TypeOrmModule.forFeature([APIKeyEntity, UserEntity, SystemConfigEntity]),
|
||||
BullModule.forRootAsync({
|
||||
useFactory: async () => ({
|
||||
prefix: 'immich_bull',
|
||||
redis: {
|
||||
host: process.env.REDIS_HOSTNAME || 'immich_redis',
|
||||
port: parseInt(process.env.REDIS_PORT || '6379'),
|
||||
db: parseInt(process.env.REDIS_DBINDEX || '0'),
|
||||
password: process.env.REDIS_PASSWORD || undefined,
|
||||
path: process.env.REDIS_SOCKET || undefined,
|
||||
},
|
||||
defaultJobOptions: {
|
||||
attempts: 3,
|
||||
removeOnComplete: true,
|
||||
removeOnFail: false,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
BullModule.registerQueue(
|
||||
{ name: QueueName.USER_DELETION },
|
||||
{ name: QueueName.THUMBNAIL_GENERATION },
|
||||
{ name: QueueName.ASSET_UPLOADED },
|
||||
{ name: QueueName.METADATA_EXTRACTION },
|
||||
{ name: QueueName.VIDEO_CONVERSION },
|
||||
{ name: QueueName.CHECKSUM_GENERATION },
|
||||
{ name: QueueName.MACHINE_LEARNING },
|
||||
{ name: QueueName.CONFIG },
|
||||
{ name: QueueName.BACKGROUND_TASK },
|
||||
),
|
||||
],
|
||||
providers: [...providers],
|
||||
exports: [...providers],
|
||||
exports: [...providers, BullModule],
|
||||
})
|
||||
export class InfraModule {}
|
||||
|
|
1
server/libs/infra/src/job/index.ts
Normal file
1
server/libs/infra/src/job/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './job.repository';
|
21
server/libs/infra/src/job/job.repository.ts
Normal file
21
server/libs/infra/src/job/job.repository.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { IJobRepository, JobItem, JobName, QueueName } from '@app/domain';
|
||||
import { InjectQueue } from '@nestjs/bull';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Queue } from 'bull';
|
||||
|
||||
export class JobRepository implements IJobRepository {
|
||||
private logger = new Logger(JobRepository.name);
|
||||
|
||||
constructor(@InjectQueue(QueueName.CONFIG) private configQueue: Queue) {}
|
||||
|
||||
async add(item: JobItem): Promise<void> {
|
||||
switch (item.name) {
|
||||
case JobName.CONFIG_CHANGE:
|
||||
await this.configQueue.add(JobName.CONFIG_CHANGE, {});
|
||||
break;
|
||||
default:
|
||||
// TODO inject remaining queues and map job to queue
|
||||
this.logger.error('Invalid job', item);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import { BullModuleOptions } from '@nestjs/bull';
|
||||
import { QueueName } from './queue-name.constant';
|
||||
|
||||
/**
|
||||
* Shared queues between apps and microservices
|
||||
*/
|
||||
export const immichSharedQueues: BullModuleOptions[] = [
|
||||
{ name: QueueName.USER_DELETION },
|
||||
{ name: QueueName.THUMBNAIL_GENERATION },
|
||||
{ name: QueueName.ASSET_UPLOADED },
|
||||
{ name: QueueName.METADATA_EXTRACTION },
|
||||
{ name: QueueName.VIDEO_CONVERSION },
|
||||
{ name: QueueName.CHECKSUM_GENERATION },
|
||||
{ name: QueueName.MACHINE_LEARNING },
|
||||
{ name: QueueName.CONFIG },
|
||||
];
|
|
@ -1,11 +0,0 @@
|
|||
export enum QueueName {
|
||||
THUMBNAIL_GENERATION = 'thumbnail-generation-queue',
|
||||
METADATA_EXTRACTION = 'metadata-extraction-queue',
|
||||
VIDEO_CONVERSION = 'video-conversion-queue',
|
||||
CHECKSUM_GENERATION = 'generate-checksum-queue',
|
||||
ASSET_UPLOADED = 'asset-uploaded-queue',
|
||||
MACHINE_LEARNING = 'machine-learning-queue',
|
||||
USER_DELETION = 'user-deletion-queue',
|
||||
CONFIG = 'config-queue',
|
||||
BACKGROUND_TASK = 'background-task',
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export * from './interfaces/asset-uploaded.interface';
|
||||
export * from './interfaces/metadata-extraction.interface';
|
||||
export * from './interfaces/video-transcode.interface';
|
||||
export * from './interfaces/thumbnail-generation.interface';
|
||||
|
||||
export * from './constants/job-name.constant';
|
||||
export * from './constants/queue-name.constant';
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"outDir": "../../dist/libs/job"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
export interface IImmichStorage {
|
||||
write(): Promise<void>;
|
||||
read(): Promise<void>;
|
||||
}
|
||||
|
||||
export enum IStorageType {}
|
|
@ -1,11 +1,10 @@
|
|||
import { AssetEntity, SystemConfigEntity } from '@app/infra';
|
||||
import { ImmichConfigModule } from '@app/immich-config';
|
||||
import { AssetEntity } from '@app/infra';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { StorageService } from './storage.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([AssetEntity, SystemConfigEntity]), ImmichConfigModule],
|
||||
imports: [TypeOrmModule.forFeature([AssetEntity])],
|
||||
providers: [StorageService],
|
||||
exports: [StorageService],
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { APP_UPLOAD_LOCATION } from '@app/common';
|
||||
import { AssetEntity, AssetType, SystemConfig } from '@app/infra';
|
||||
import { ImmichConfigService, INITIAL_SYSTEM_CONFIG } from '@app/immich-config';
|
||||
import { SystemConfigService, INITIAL_SYSTEM_CONFIG } from '@app/domain';
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import fsPromise from 'fs/promises';
|
||||
|
@ -19,7 +19,7 @@ import {
|
|||
supportedMonthTokens,
|
||||
supportedSecondTokens,
|
||||
supportedYearTokens,
|
||||
} from './constants/supported-datetime-template';
|
||||
} from '@app/domain';
|
||||
|
||||
const moveFile = promisify<string, string, mv.Options>(mv);
|
||||
|
||||
|
@ -32,14 +32,14 @@ export class StorageService {
|
|||
constructor(
|
||||
@InjectRepository(AssetEntity)
|
||||
private assetRepository: Repository<AssetEntity>,
|
||||
private immichConfigService: ImmichConfigService,
|
||||
private systemConfigService: SystemConfigService,
|
||||
@Inject(INITIAL_SYSTEM_CONFIG) config: SystemConfig,
|
||||
) {
|
||||
this.storageTemplate = this.compile(config.storageTemplate.template);
|
||||
|
||||
this.immichConfigService.addValidator((config) => this.validateConfig(config));
|
||||
this.systemConfigService.addValidator((config) => this.validateConfig(config));
|
||||
|
||||
this.immichConfigService.config$.subscribe((config) => {
|
||||
this.systemConfigService.config$.subscribe((config) => {
|
||||
this.logger.debug(`Received new config, recompiling storage template: ${config.storageTemplate.template}`);
|
||||
this.storageTemplate = this.compile(config.storageTemplate.template);
|
||||
});
|
||||
|
|
|
@ -136,7 +136,8 @@
|
|||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
"**/*.(t|j)s",
|
||||
"!**/migrations/*"
|
||||
],
|
||||
"coverageDirectory": "./coverage",
|
||||
"coverageThreshold": {
|
||||
|
@ -145,10 +146,10 @@
|
|||
"statements": 20
|
||||
},
|
||||
"./libs/domain/": {
|
||||
"branches": 70,
|
||||
"branches": 75,
|
||||
"functions": 85,
|
||||
"lines": 85,
|
||||
"statements": 85
|
||||
"lines": 90,
|
||||
"statements": 90
|
||||
}
|
||||
},
|
||||
"testEnvironment": "node",
|
||||
|
@ -158,9 +159,6 @@
|
|||
],
|
||||
"moduleNameMapper": {
|
||||
"@app/common": "<rootDir>/libs/common/src",
|
||||
"^@app/job(|/.*)$": "<rootDir>/libs/job/src/$1",
|
||||
"@app/job": "<rootDir>/libs/job/src",
|
||||
"^@app/immich-config(|/.*)$": "<rootDir>/libs/immich-config/src/$1",
|
||||
"^@app/storage(|/.*)$": "<rootDir>/libs/storage/src/$1",
|
||||
"^@app/infra(|/.*)$": "<rootDir>/libs/infra/src/$1",
|
||||
"^@app/domain(|/.*)$": "<rootDir>/libs/domain/src/$1"
|
||||
|
|
|
@ -18,10 +18,6 @@
|
|||
"paths": {
|
||||
"@app/common": ["libs/common/src"],
|
||||
"@app/common/*": ["libs/common/src/*"],
|
||||
"@app/job": ["libs/job/src"],
|
||||
"@app/job/*": ["libs/job/src/*"],
|
||||
"@app/immich-config": ["libs/immich-config/src"],
|
||||
"@app/immich-config/*": ["libs/immich-config/src/*"],
|
||||
"@app/storage": ["libs/storage/src"],
|
||||
"@app/storage/*": ["libs/storage/src/*"],
|
||||
"@app/infra": ["libs/infra/src"],
|
||||
|
|
Loading…
Reference in a new issue