mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +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:
		| @@ -5,7 +5,7 @@ DB_PASSWORD=postgres | |||||||
| DB_DATABASE_NAME=e2e_test | DB_DATABASE_NAME=e2e_test | ||||||
|  |  | ||||||
| # Redis | # Redis | ||||||
| REDIS_HOSTNAME=immich_redis_test | REDIS_HOSTNAME=immich-redis-test | ||||||
|  |  | ||||||
| # Upload File Config | # Upload File Config | ||||||
| UPLOAD_LOCATION=./upload | UPLOAD_LOCATION=./upload | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ import { AssetService } from './asset.service'; | |||||||
| import { AssetController } from './asset.controller'; | import { AssetController } from './asset.controller'; | ||||||
| import { TypeOrmModule } from '@nestjs/typeorm'; | import { TypeOrmModule } from '@nestjs/typeorm'; | ||||||
| import { AssetEntity } from '@app/infra'; | import { AssetEntity } from '@app/infra'; | ||||||
| import { BullModule } from '@nestjs/bull'; |  | ||||||
| import { BackgroundTaskModule } from '../../modules/background-task/background-task.module'; | import { BackgroundTaskModule } from '../../modules/background-task/background-task.module'; | ||||||
| import { BackgroundTaskService } from '../../modules/background-task/background-task.service'; | import { BackgroundTaskService } from '../../modules/background-task/background-task.service'; | ||||||
| import { CommunicationModule } from '../communication/communication.module'; | import { CommunicationModule } from '../communication/communication.module'; | ||||||
| @@ -12,7 +11,6 @@ import { DownloadModule } from '../../modules/download/download.module'; | |||||||
| import { TagModule } from '../tag/tag.module'; | import { TagModule } from '../tag/tag.module'; | ||||||
| import { AlbumModule } from '../album/album.module'; | import { AlbumModule } from '../album/album.module'; | ||||||
| import { StorageModule } from '@app/storage'; | import { StorageModule } from '@app/storage'; | ||||||
| import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.constant'; |  | ||||||
| import { ShareModule } from '../share/share.module'; | import { ShareModule } from '../share/share.module'; | ||||||
|  |  | ||||||
| const ASSET_REPOSITORY_PROVIDER = { | const ASSET_REPOSITORY_PROVIDER = { | ||||||
| @@ -29,7 +27,6 @@ const ASSET_REPOSITORY_PROVIDER = { | |||||||
|     TagModule, |     TagModule, | ||||||
|     StorageModule, |     StorageModule, | ||||||
|     forwardRef(() => AlbumModule), |     forwardRef(() => AlbumModule), | ||||||
|     BullModule.registerQueue(...immichSharedQueues), |  | ||||||
|     ShareModule, |     ShareModule, | ||||||
|   ], |   ], | ||||||
|   controllers: [AssetController], |   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 { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto'; | ||||||
| import { DownloadService } from '../../modules/download/download.service'; | import { DownloadService } from '../../modules/download/download.service'; | ||||||
| import { BackgroundTaskService } from '../../modules/background-task/background-task.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 { Queue } from 'bull'; | ||||||
| import { IAlbumRepository } from '../album/album-repository'; | import { IAlbumRepository } from '../album/album-repository'; | ||||||
| import { StorageService } from '@app/storage'; | 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 { UpdateAssetDto } from './dto/update-asset.dto'; | ||||||
| import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto'; | import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto'; | ||||||
| import { BackgroundTaskService } from '../../modules/background-task/background-task.service'; | 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 { InjectQueue } from '@nestjs/bull'; | ||||||
| import { Queue } from 'bull'; | import { Queue } from 'bull'; | ||||||
| import { DownloadService } from '../../modules/download/download.service'; | import { DownloadService } from '../../modules/download/download.service'; | ||||||
|   | |||||||
| @@ -1,12 +1,11 @@ | |||||||
| import { Module } from '@nestjs/common'; | import { Module } from '@nestjs/common'; | ||||||
| import { ImmichConfigModule } from '@app/immich-config'; |  | ||||||
| import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module'; | import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module'; | ||||||
| import { OAuthModule } from '../oauth/oauth.module'; | import { OAuthModule } from '../oauth/oauth.module'; | ||||||
| import { AuthController } from './auth.controller'; | import { AuthController } from './auth.controller'; | ||||||
| import { AuthService } from './auth.service'; | import { AuthService } from './auth.service'; | ||||||
|  |  | ||||||
| @Module({ | @Module({ | ||||||
|   imports: [ImmichJwtModule, OAuthModule, ImmichConfigModule], |   imports: [ImmichJwtModule, OAuthModule], | ||||||
|   controllers: [AuthController], |   controllers: [AuthController], | ||||||
|   providers: [AuthService], |   providers: [AuthService], | ||||||
| }) | }) | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ import { UserEntity } from '@app/infra'; | |||||||
| import { BadRequestException, UnauthorizedException } from '@nestjs/common'; | import { BadRequestException, UnauthorizedException } from '@nestjs/common'; | ||||||
| import * as bcrypt from 'bcrypt'; | import * as bcrypt from 'bcrypt'; | ||||||
| import { SystemConfig } from '@app/infra'; | import { SystemConfig } from '@app/infra'; | ||||||
| import { ImmichConfigService } from '@app/immich-config'; | import { SystemConfigService } from '@app/domain'; | ||||||
| import { AuthType } from '../../constants/jwt.constant'; | import { AuthType } from '../../constants/jwt.constant'; | ||||||
| import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service'; | import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service'; | ||||||
| import { OAuthService } from '../oauth/oauth.service'; | import { OAuthService } from '../oauth/oauth.service'; | ||||||
| @@ -50,7 +50,7 @@ describe('AuthService', () => { | |||||||
|   let sut: AuthService; |   let sut: AuthService; | ||||||
|   let userRepositoryMock: jest.Mocked<IUserRepository>; |   let userRepositoryMock: jest.Mocked<IUserRepository>; | ||||||
|   let immichJwtServiceMock: jest.Mocked<ImmichJwtService>; |   let immichJwtServiceMock: jest.Mocked<ImmichJwtService>; | ||||||
|   let immichConfigServiceMock: jest.Mocked<ImmichConfigService>; |   let immichConfigServiceMock: jest.Mocked<SystemConfigService>; | ||||||
|   let oauthServiceMock: jest.Mocked<OAuthService>; |   let oauthServiceMock: jest.Mocked<OAuthService>; | ||||||
|   let compare: jest.Mock; |   let compare: jest.Mock; | ||||||
|  |  | ||||||
| @@ -89,7 +89,7 @@ describe('AuthService', () => { | |||||||
|  |  | ||||||
|     immichConfigServiceMock = { |     immichConfigServiceMock = { | ||||||
|       config$: { subscribe: jest.fn() }, |       config$: { subscribe: jest.fn() }, | ||||||
|     } as unknown as jest.Mocked<ImmichConfigService>; |     } as unknown as jest.Mocked<SystemConfigService>; | ||||||
|  |  | ||||||
|     sut = new AuthService( |     sut = new AuthService( | ||||||
|       oauthServiceMock, |       oauthServiceMock, | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ import { LoginResponseDto } from './response-dto/login-response.dto'; | |||||||
| import { LogoutResponseDto } from './response-dto/logout-response.dto'; | import { LogoutResponseDto } from './response-dto/logout-response.dto'; | ||||||
| import { OAuthService } from '../oauth/oauth.service'; | import { OAuthService } from '../oauth/oauth.service'; | ||||||
| import { UserCore } from '@app/domain'; | 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'; | import { SystemConfig } from '@app/infra'; | ||||||
|  |  | ||||||
| @Injectable() | @Injectable() | ||||||
| @@ -32,7 +32,7 @@ export class AuthService { | |||||||
|     private oauthService: OAuthService, |     private oauthService: OAuthService, | ||||||
|     private immichJwtService: ImmichJwtService, |     private immichJwtService: ImmichJwtService, | ||||||
|     @Inject(IUserRepository) userRepository: IUserRepository, |     @Inject(IUserRepository) userRepository: IUserRepository, | ||||||
|     private configService: ImmichConfigService, |     private configService: SystemConfigService, | ||||||
|     @Inject(INITIAL_SYSTEM_CONFIG) private config: SystemConfig, |     @Inject(INITIAL_SYSTEM_CONFIG) private config: SystemConfig, | ||||||
|   ) { |   ) { | ||||||
|     this.userCore = new UserCore(userRepository); |     this.userCore = new UserCore(userRepository); | ||||||
|   | |||||||
| @@ -6,20 +6,10 @@ import { TypeOrmModule } from '@nestjs/typeorm'; | |||||||
| import { ExifEntity } from '@app/infra'; | import { ExifEntity } from '@app/infra'; | ||||||
| import { TagModule } from '../tag/tag.module'; | import { TagModule } from '../tag/tag.module'; | ||||||
| import { AssetModule } from '../asset/asset.module'; | import { AssetModule } from '../asset/asset.module'; | ||||||
|  |  | ||||||
| import { StorageModule } from '@app/storage'; | import { StorageModule } from '@app/storage'; | ||||||
| import { BullModule } from '@nestjs/bull'; |  | ||||||
| import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.constant'; |  | ||||||
|  |  | ||||||
| @Module({ | @Module({ | ||||||
|   imports: [ |   imports: [TypeOrmModule.forFeature([ExifEntity]), ImmichJwtModule, TagModule, AssetModule, StorageModule], | ||||||
|     TypeOrmModule.forFeature([ExifEntity]), |  | ||||||
|     ImmichJwtModule, |  | ||||||
|     TagModule, |  | ||||||
|     AssetModule, |  | ||||||
|     StorageModule, |  | ||||||
|     BullModule.registerQueue(...immichSharedQueues), |  | ||||||
|   ], |  | ||||||
|   controllers: [JobController], |   controllers: [JobController], | ||||||
|   providers: [JobService], |   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 { InjectQueue } from '@nestjs/bull'; | ||||||
| import { Queue } from 'bull'; | import { Queue } from 'bull'; | ||||||
| import { BadRequestException, Inject, Injectable } from '@nestjs/common'; | import { BadRequestException, Inject, Injectable } from '@nestjs/common'; | ||||||
| @@ -7,7 +14,6 @@ import { IAssetRepository } from '../asset/asset-repository'; | |||||||
| import { AssetType } from '@app/infra'; | import { AssetType } from '@app/infra'; | ||||||
| import { GetJobDto, JobId } from './dto/get-job.dto'; | import { GetJobDto, JobId } from './dto/get-job.dto'; | ||||||
| import { JobStatusResponseDto } from './response-dto/job-status-response.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 { StorageService } from '@app/storage'; | ||||||
| import { MACHINE_LEARNING_ENABLED } from '@app/common'; | import { MACHINE_LEARNING_ENABLED } from '@app/common'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,11 +1,10 @@ | |||||||
| import { ImmichConfigModule } from '@app/immich-config'; |  | ||||||
| import { Module } from '@nestjs/common'; | import { Module } from '@nestjs/common'; | ||||||
| import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module'; | import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module'; | ||||||
| import { OAuthController } from './oauth.controller'; | import { OAuthController } from './oauth.controller'; | ||||||
| import { OAuthService } from './oauth.service'; | import { OAuthService } from './oauth.service'; | ||||||
|  |  | ||||||
| @Module({ | @Module({ | ||||||
|   imports: [ImmichJwtModule, ImmichConfigModule], |   imports: [ImmichJwtModule], | ||||||
|   controllers: [OAuthController], |   controllers: [OAuthController], | ||||||
|   providers: [OAuthService], |   providers: [OAuthService], | ||||||
|   exports: [OAuthService], |   exports: [OAuthService], | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { SystemConfig, UserEntity } from '@app/infra'; | import { SystemConfig, UserEntity } from '@app/infra'; | ||||||
| import { ImmichConfigService } from '@app/immich-config'; | import { SystemConfigService } from '@app/domain'; | ||||||
| import { BadRequestException } from '@nestjs/common'; | import { BadRequestException } from '@nestjs/common'; | ||||||
| import { generators, Issuer } from 'openid-client'; | import { generators, Issuer } from 'openid-client'; | ||||||
| import { AuthUserDto } from '../../decorators/auth-user.decorator'; | import { AuthUserDto } from '../../decorators/auth-user.decorator'; | ||||||
| @@ -86,7 +86,7 @@ jest.mock('@nestjs/common', () => ({ | |||||||
| describe('OAuthService', () => { | describe('OAuthService', () => { | ||||||
|   let sut: OAuthService; |   let sut: OAuthService; | ||||||
|   let userRepositoryMock: jest.Mocked<IUserRepository>; |   let userRepositoryMock: jest.Mocked<IUserRepository>; | ||||||
|   let immichConfigServiceMock: jest.Mocked<ImmichConfigService>; |   let immichConfigServiceMock: jest.Mocked<SystemConfigService>; | ||||||
|   let immichJwtServiceMock: jest.Mocked<ImmichJwtService>; |   let immichJwtServiceMock: jest.Mocked<ImmichJwtService>; | ||||||
|   let callbackMock: jest.Mock; |   let callbackMock: jest.Mock; | ||||||
|  |  | ||||||
| @@ -132,7 +132,7 @@ describe('OAuthService', () => { | |||||||
|  |  | ||||||
|     immichConfigServiceMock = { |     immichConfigServiceMock = { | ||||||
|       config$: { subscribe: jest.fn() }, |       config$: { subscribe: jest.fn() }, | ||||||
|     } as unknown as jest.Mocked<ImmichConfigService>; |     } as unknown as jest.Mocked<SystemConfigService>; | ||||||
|  |  | ||||||
|     sut = new OAuthService(immichJwtServiceMock, immichConfigServiceMock, userRepositoryMock, config.disabled); |     sut = new OAuthService(immichJwtServiceMock, immichConfigServiceMock, userRepositoryMock, config.disabled); | ||||||
|   }); |   }); | ||||||
|   | |||||||
| @@ -1,11 +1,10 @@ | |||||||
| import { SystemConfig } from '@app/infra'; | import { SystemConfig } from '@app/infra'; | ||||||
| import { ImmichConfigService, INITIAL_SYSTEM_CONFIG } from '@app/immich-config'; |  | ||||||
| import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'; | import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'; | ||||||
| import { ClientMetadata, custom, generators, Issuer, UserinfoResponse } from 'openid-client'; | import { ClientMetadata, custom, generators, Issuer, UserinfoResponse } from 'openid-client'; | ||||||
| import { AuthUserDto } from '../../decorators/auth-user.decorator'; | import { AuthUserDto } from '../../decorators/auth-user.decorator'; | ||||||
| import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service'; | import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service'; | ||||||
| import { LoginResponseDto } from '../auth/response-dto/login-response.dto'; | 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 { OAuthCallbackDto } from './dto/oauth-auth-code.dto'; | ||||||
| import { OAuthConfigDto } from './dto/oauth-config.dto'; | import { OAuthConfigDto } from './dto/oauth-config.dto'; | ||||||
| import { OAuthConfigResponseDto } from './response-dto/oauth-config-response.dto'; | import { OAuthConfigResponseDto } from './response-dto/oauth-config-response.dto'; | ||||||
| @@ -23,7 +22,7 @@ export class OAuthService { | |||||||
|  |  | ||||||
|   constructor( |   constructor( | ||||||
|     private immichJwtService: ImmichJwtService, |     private immichJwtService: ImmichJwtService, | ||||||
|     immichConfigService: ImmichConfigService, |     configService: SystemConfigService, | ||||||
|     @Inject(IUserRepository) userRepository: IUserRepository, |     @Inject(IUserRepository) userRepository: IUserRepository, | ||||||
|     @Inject(INITIAL_SYSTEM_CONFIG) private config: SystemConfig, |     @Inject(INITIAL_SYSTEM_CONFIG) private config: SystemConfig, | ||||||
|   ) { |   ) { | ||||||
| @@ -33,7 +32,7 @@ export class OAuthService { | |||||||
|       timeout: 30000, |       timeout: 30000, | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     immichConfigService.config$.subscribe((config) => (this.config = config)); |     configService.config$.subscribe((config) => (this.config = config)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async generateConfig(dto: OAuthConfigDto): Promise<OAuthConfigResponseDto> { |   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 { Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'; | ||||||
| import { ApiExcludeEndpoint } from '@nestjs/swagger'; | import { ApiExcludeEndpoint } from '@nestjs/swagger'; | ||||||
| import { ImmichConfigService } from '@app/immich-config'; | import { SystemConfigService } from '@app/domain'; | ||||||
|  |  | ||||||
| @Controller() | @Controller() | ||||||
| export class AppController { | export class AppController { | ||||||
|   constructor(private configService: ImmichConfigService) {} |   constructor(private configService: SystemConfigService) {} | ||||||
|  |  | ||||||
|   @ApiExcludeEndpoint() |   @ApiExcludeEndpoint() | ||||||
|   @Post('refresh-config') |   @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 { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; | ||||||
| import { AssetModule } from './api-v1/asset/asset.module'; | import { AssetModule } from './api-v1/asset/asset.module'; | ||||||
| import { AuthModule } from './api-v1/auth/auth.module'; | import { AuthModule } from './api-v1/auth/auth.module'; | ||||||
| import { ImmichJwtModule } from './modules/immich-jwt/immich-jwt.module'; | import { ImmichJwtModule } from './modules/immich-jwt/immich-jwt.module'; | ||||||
| import { DeviceInfoModule } from './api-v1/device-info/device-info.module'; | import { DeviceInfoModule } from './api-v1/device-info/device-info.module'; | ||||||
| import { ConfigModule } from '@nestjs/config'; | import { ConfigModule } from '@nestjs/config'; | ||||||
| import { BullModule } from '@nestjs/bull'; |  | ||||||
| import { ServerInfoModule } from './api-v1/server-info/server-info.module'; | import { ServerInfoModule } from './api-v1/server-info/server-info.module'; | ||||||
| import { BackgroundTaskModule } from './modules/background-task/background-task.module'; | import { BackgroundTaskModule } from './modules/background-task/background-task.module'; | ||||||
| import { CommunicationModule } from './api-v1/communication/communication.module'; | import { CommunicationModule } from './api-v1/communication/communication.module'; | ||||||
| @@ -14,14 +13,12 @@ import { AppController } from './app.controller'; | |||||||
| import { ScheduleModule } from '@nestjs/schedule'; | import { ScheduleModule } from '@nestjs/schedule'; | ||||||
| import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module'; | import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module'; | ||||||
| import { JobModule } from './api-v1/job/job.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 { OAuthModule } from './api-v1/oauth/oauth.module'; | ||||||
| import { TagModule } from './api-v1/tag/tag.module'; | import { TagModule } from './api-v1/tag/tag.module'; | ||||||
| import { ImmichConfigModule } from '@app/immich-config'; |  | ||||||
| import { ShareModule } from './api-v1/share/share.module'; | import { ShareModule } from './api-v1/share/share.module'; | ||||||
| import { DomainModule } from '@app/domain'; | import { DomainModule } from '@app/domain'; | ||||||
| import { InfraModule } from '@app/infra'; | import { InfraModule } from '@app/infra'; | ||||||
| import { APIKeyController, UserController } from './controllers'; | import { APIKeyController, SystemConfigController, UserController } from './controllers'; | ||||||
|  |  | ||||||
| @Module({ | @Module({ | ||||||
|   imports: [ |   imports: [ | ||||||
| @@ -37,12 +34,9 @@ import { APIKeyController, UserController } from './controllers'; | |||||||
|     OAuthModule, |     OAuthModule, | ||||||
|  |  | ||||||
|     ImmichJwtModule, |     ImmichJwtModule, | ||||||
|     ImmichConfigModule, |  | ||||||
|  |  | ||||||
|     DeviceInfoModule, |     DeviceInfoModule, | ||||||
|  |  | ||||||
|     BullModule.forRootAsync(immichBullAsyncConfig), |  | ||||||
|  |  | ||||||
|     ServerInfoModule, |     ServerInfoModule, | ||||||
|  |  | ||||||
|     BackgroundTaskModule, |     BackgroundTaskModule, | ||||||
| @@ -57,8 +51,6 @@ import { APIKeyController, UserController } from './controllers'; | |||||||
|  |  | ||||||
|     JobModule, |     JobModule, | ||||||
|  |  | ||||||
|     SystemConfigModule, |  | ||||||
|  |  | ||||||
|     TagModule, |     TagModule, | ||||||
|  |  | ||||||
|     ShareModule, |     ShareModule, | ||||||
| @@ -67,6 +59,7 @@ import { APIKeyController, UserController } from './controllers'; | |||||||
|     // |     // | ||||||
|     AppController, |     AppController, | ||||||
|     APIKeyController, |     APIKeyController, | ||||||
|  |     SystemConfigController, | ||||||
|     UserController, |     UserController, | ||||||
|   ], |   ], | ||||||
|   providers: [], |   providers: [], | ||||||
|   | |||||||
| @@ -1,2 +1,3 @@ | |||||||
| export * from './api-key.controller'; | export * from './api-key.controller'; | ||||||
|  | export * from './system-config.controller'; | ||||||
| export * from './user.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 { Body, Controller, Get, Put, ValidationPipe } from '@nestjs/common'; | ||||||
| import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; | ||||||
| import { Authenticated } from '../../decorators/authenticated.decorator'; | 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'; |  | ||||||
| 
 | 
 | ||||||
| @ApiTags('System Config') | @ApiTags('System Config') | ||||||
| @ApiBearerAuth() | @ApiBearerAuth() | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { BullModule } from '@nestjs/bull'; | import { BullModule } from '@nestjs/bull'; | ||||||
| import { Module } from '@nestjs/common'; | import { Module } from '@nestjs/common'; | ||||||
| import { QueueName } from '@app/job'; | import { QueueName } from '@app/domain'; | ||||||
| import { BackgroundTaskProcessor } from './background-task.processor'; | import { BackgroundTaskProcessor } from './background-task.processor'; | ||||||
| import { BackgroundTaskService } from './background-task.service'; | import { BackgroundTaskService } from './background-task.service'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { assetUtils } from '@app/common/utils'; | import { assetUtils } from '@app/common/utils'; | ||||||
| import { Process, Processor } from '@nestjs/bull'; | import { Process, Processor } from '@nestjs/bull'; | ||||||
| import { Job } from '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'; | import { AssetResponseDto } from '../../api-v1/asset/response-dto/asset-response.dto'; | ||||||
|  |  | ||||||
| @Processor(QueueName.BACKGROUND_TASK) | @Processor(QueueName.BACKGROUND_TASK) | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { InjectQueue } from '@nestjs/bull/dist/decorators'; | import { InjectQueue } from '@nestjs/bull/dist/decorators'; | ||||||
| import { Injectable } from '@nestjs/common'; | import { Injectable } from '@nestjs/common'; | ||||||
| import { Queue } from 'bull'; | 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'; | import { AssetResponseDto } from '../../api-v1/asset/response-dto/asset-response.dto'; | ||||||
|  |  | ||||||
| @Injectable() | @Injectable() | ||||||
|   | |||||||
| @@ -1,15 +1,10 @@ | |||||||
| import { BullModule } from '@nestjs/bull'; |  | ||||||
| import { Module } from '@nestjs/common'; | import { Module } from '@nestjs/common'; | ||||||
| import { TypeOrmModule } from '@nestjs/typeorm'; | import { TypeOrmModule } from '@nestjs/typeorm'; | ||||||
| import { AssetEntity, ExifEntity, UserEntity } from '@app/infra'; | import { AssetEntity, ExifEntity, UserEntity } from '@app/infra'; | ||||||
| import { ScheduleTasksService } from './schedule-tasks.service'; | import { ScheduleTasksService } from './schedule-tasks.service'; | ||||||
| import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.constant'; |  | ||||||
|  |  | ||||||
| @Module({ | @Module({ | ||||||
|   imports: [ |   imports: [TypeOrmModule.forFeature([AssetEntity, ExifEntity, UserEntity])], | ||||||
|     TypeOrmModule.forFeature([AssetEntity, ExifEntity, UserEntity]), |  | ||||||
|     BullModule.registerQueue(...immichSharedQueues), |  | ||||||
|   ], |  | ||||||
|   providers: [ScheduleTasksService], |   providers: [ScheduleTasksService], | ||||||
| }) | }) | ||||||
| export class ScheduleTasksModule {} | export class ScheduleTasksModule {} | ||||||
|   | |||||||
| @@ -5,9 +5,9 @@ import { IsNull, Not, Repository } from 'typeorm'; | |||||||
| import { AssetEntity, AssetType, ExifEntity, UserEntity } from '@app/infra'; | import { AssetEntity, AssetType, ExifEntity, UserEntity } from '@app/infra'; | ||||||
| import { InjectQueue } from '@nestjs/bull'; | import { InjectQueue } from '@nestjs/bull'; | ||||||
| import { Queue } from '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 { ConfigService } from '@nestjs/config'; | ||||||
| import { IUserDeletionJob } from '@app/job/interfaces/user-deletion.interface'; | import { IUserDeletionJob } from '@app/domain'; | ||||||
| import { userUtils } from '@app/common'; | import { userUtils } from '@app/common'; | ||||||
|  |  | ||||||
| @Injectable() | @Injectable() | ||||||
|   | |||||||
| @@ -8,8 +8,6 @@ | |||||||
|   }, |   }, | ||||||
|   "moduleNameMapper": { |   "moduleNameMapper": { | ||||||
|     "^@app/common": "<rootDir>../../../libs/common/src", |     "^@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/storage(|/.*)$": "<rootDir>../../../libs/storage/src/$1", | ||||||
|     "^@app/infra(|/.*)$": "<rootDir>../../../libs/infra/src/$1", |     "^@app/infra(|/.*)$": "<rootDir>../../../libs/infra/src/$1", | ||||||
|     "^@app/domain(|/.*)$": "<rootDir>../../../libs/domain/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 { AssetEntity, ExifEntity, SmartInfoEntity, UserEntity, APIKeyEntity, InfraModule } from '@app/infra'; | ||||||
| import { StorageModule } from '@app/storage'; | import { StorageModule } from '@app/storage'; | ||||||
| import { BullModule } from '@nestjs/bull'; |  | ||||||
| import { Module } from '@nestjs/common'; | import { Module } from '@nestjs/common'; | ||||||
| import { ConfigModule } from '@nestjs/config'; | import { ConfigModule } from '@nestjs/config'; | ||||||
| import { TypeOrmModule } from '@nestjs/typeorm'; | import { TypeOrmModule } from '@nestjs/typeorm'; | ||||||
| import { ImmichConfigModule } from 'libs/immich-config/src'; |  | ||||||
| import { CommunicationModule } from '../../immich/src/api-v1/communication/communication.module'; | import { CommunicationModule } from '../../immich/src/api-v1/communication/communication.module'; | ||||||
| import { MicroservicesService } from './microservices.service'; | import { MicroservicesService } from './microservices.service'; | ||||||
| import { AssetUploadedProcessor } from './processors/asset-uploaded.processor'; | 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 { ThumbnailGeneratorProcessor } from './processors/thumbnail.processor'; | ||||||
| import { UserDeletionProcessor } from './processors/user-deletion.processor'; | import { UserDeletionProcessor } from './processors/user-deletion.processor'; | ||||||
| import { VideoTranscodeProcessor } from './processors/video-transcode.processor'; | import { VideoTranscodeProcessor } from './processors/video-transcode.processor'; | ||||||
| import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.constant'; |  | ||||||
| import { DomainModule } from '@app/domain'; | import { DomainModule } from '@app/domain'; | ||||||
|  |  | ||||||
| @Module({ | @Module({ | ||||||
| @@ -25,11 +22,8 @@ import { DomainModule } from '@app/domain'; | |||||||
|     DomainModule.register({ |     DomainModule.register({ | ||||||
|       imports: [InfraModule], |       imports: [InfraModule], | ||||||
|     }), |     }), | ||||||
|     ImmichConfigModule, |  | ||||||
|     TypeOrmModule.forFeature([UserEntity, ExifEntity, AssetEntity, SmartInfoEntity, APIKeyEntity]), |     TypeOrmModule.forFeature([UserEntity, ExifEntity, AssetEntity, SmartInfoEntity, APIKeyEntity]), | ||||||
|     StorageModule, |     StorageModule, | ||||||
|     BullModule.forRootAsync(immichBullAsyncConfig), |  | ||||||
|     BullModule.registerQueue(...immichSharedQueues), |  | ||||||
|     CommunicationModule, |     CommunicationModule, | ||||||
|   ], |   ], | ||||||
|   controllers: [], |   controllers: [], | ||||||
| @@ -44,6 +38,5 @@ import { DomainModule } from '@app/domain'; | |||||||
|     UserDeletionProcessor, |     UserDeletionProcessor, | ||||||
|     StorageMigrationProcessor, |     StorageMigrationProcessor, | ||||||
|   ], |   ], | ||||||
|   exports: [BullModule], |  | ||||||
| }) | }) | ||||||
| export class MicroservicesModule {} | export class MicroservicesModule {} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { QueueName } from '@app/job'; | import { QueueName } from '@app/domain'; | ||||||
| import { InjectQueue } from '@nestjs/bull'; | import { InjectQueue } from '@nestjs/bull'; | ||||||
| import { Injectable, OnModuleInit } from '@nestjs/common'; | import { Injectable, OnModuleInit } from '@nestjs/common'; | ||||||
| import { Queue } from 'bull'; | import { Queue } from 'bull'; | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import { | |||||||
|   IVideoTranscodeJob, |   IVideoTranscodeJob, | ||||||
|   QueueName, |   QueueName, | ||||||
|   JobName, |   JobName, | ||||||
| } from '@app/job'; | } from '@app/domain'; | ||||||
| import { InjectQueue, Process, Processor } from '@nestjs/bull'; | import { InjectQueue, Process, Processor } from '@nestjs/bull'; | ||||||
| import { Job, Queue } from 'bull'; | import { Job, Queue } from 'bull'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { AssetEntity } from '@app/infra'; | import { AssetEntity } from '@app/infra'; | ||||||
| import { QueueName } from '@app/job'; | import { QueueName } from '@app/domain'; | ||||||
| import { Process, Processor } from '@nestjs/bull'; | import { Process, Processor } from '@nestjs/bull'; | ||||||
| import { Logger } from '@nestjs/common'; | import { Logger } from '@nestjs/common'; | ||||||
| import { InjectRepository } from '@nestjs/typeorm'; | import { InjectRepository } from '@nestjs/typeorm'; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { AssetEntity } from '@app/infra'; | import { AssetEntity } from '@app/infra'; | ||||||
| import { SmartInfoEntity } from '@app/infra'; | import { SmartInfoEntity } from '@app/infra'; | ||||||
| import { QueueName, JobName } from '@app/job'; | import { QueueName, JobName } from '@app/domain'; | ||||||
| import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface'; | import { IMachineLearningJob } from '@app/domain'; | ||||||
| import { Process, Processor } from '@nestjs/bull'; | import { Process, Processor } from '@nestjs/bull'; | ||||||
| import { Logger } from '@nestjs/common'; | import { Logger } from '@nestjs/common'; | ||||||
| import { InjectRepository } from '@nestjs/typeorm'; | import { InjectRepository } from '@nestjs/typeorm'; | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import { | |||||||
|   IVideoLengthExtractionProcessor, |   IVideoLengthExtractionProcessor, | ||||||
|   QueueName, |   QueueName, | ||||||
|   JobName, |   JobName, | ||||||
| } from '@app/job'; | } from '@app/domain'; | ||||||
| import { Process, Processor } from '@nestjs/bull'; | import { Process, Processor } from '@nestjs/bull'; | ||||||
| import { Logger } from '@nestjs/common'; | import { Logger } from '@nestjs/common'; | ||||||
| import { ConfigService } from '@nestjs/config'; | import { ConfigService } from '@nestjs/config'; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { APP_UPLOAD_LOCATION } from '@app/common'; | import { APP_UPLOAD_LOCATION } from '@app/common'; | ||||||
| import { AssetEntity } from '@app/infra'; | import { AssetEntity } from '@app/infra'; | ||||||
| import { ImmichConfigService } from '@app/immich-config'; | import { SystemConfigService } from '@app/domain'; | ||||||
| import { QueueName, JobName } from '@app/job'; | import { QueueName, JobName } from '@app/domain'; | ||||||
| import { StorageService } from '@app/storage'; | import { StorageService } from '@app/storage'; | ||||||
| import { Process, Processor } from '@nestjs/bull'; | import { Process, Processor } from '@nestjs/bull'; | ||||||
| import { Logger } from '@nestjs/common'; | import { Logger } from '@nestjs/common'; | ||||||
| @@ -14,7 +14,7 @@ export class StorageMigrationProcessor { | |||||||
|  |  | ||||||
|   constructor( |   constructor( | ||||||
|     private storageService: StorageService, |     private storageService: StorageService, | ||||||
|     private immichConfigService: ImmichConfigService, |     private systemConfigService: SystemConfigService, | ||||||
|  |  | ||||||
|     @InjectRepository(AssetEntity) |     @InjectRepository(AssetEntity) | ||||||
|     private assetRepository: Repository<AssetEntity>, |     private assetRepository: Repository<AssetEntity>, | ||||||
| @@ -56,6 +56,6 @@ export class StorageMigrationProcessor { | |||||||
|    */ |    */ | ||||||
|   @Process({ name: JobName.CONFIG_CHANGE, concurrency: 1 }) |   @Process({ name: JobName.CONFIG_CHANGE, concurrency: 1 }) | ||||||
|   async updateTemplate() { |   async updateTemplate() { | ||||||
|     await this.immichConfigService.refreshConfig(); |     await this.systemConfigService.refreshConfig(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { APP_UPLOAD_LOCATION } from '@app/common'; | import { APP_UPLOAD_LOCATION } from '@app/common'; | ||||||
| import { AssetEntity, AssetType } from '@app/infra'; | 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 { InjectQueue, Process, Processor } from '@nestjs/bull'; | ||||||
| import { Logger } from '@nestjs/common'; | import { Logger } from '@nestjs/common'; | ||||||
| import { InjectRepository } from '@nestjs/typeorm'; | import { InjectRepository } from '@nestjs/typeorm'; | ||||||
| @@ -13,7 +13,7 @@ import sharp from 'sharp'; | |||||||
| import { Repository } from 'typeorm/repository/Repository'; | import { Repository } from 'typeorm/repository/Repository'; | ||||||
| import { join } from 'path'; | import { join } from 'path'; | ||||||
| import { CommunicationGateway } from 'apps/immich/src/api-v1/communication/communication.gateway'; | 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) | @Processor(QueueName.THUMBNAIL_GENERATION) | ||||||
| export class ThumbnailGeneratorProcessor { | export class ThumbnailGeneratorProcessor { | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { APP_UPLOAD_LOCATION, userUtils } from '@app/common'; | import { APP_UPLOAD_LOCATION, userUtils } from '@app/common'; | ||||||
| import { APIKeyEntity, AssetEntity, UserEntity } from '@app/infra'; | import { APIKeyEntity, AssetEntity, UserEntity } from '@app/infra'; | ||||||
| import { QueueName, JobName } from '@app/job'; | import { QueueName, JobName } from '@app/domain'; | ||||||
| import { IUserDeletionJob } from '@app/job/interfaces/user-deletion.interface'; | import { IUserDeletionJob } from '@app/domain'; | ||||||
| import { Process, Processor } from '@nestjs/bull'; | import { Process, Processor } from '@nestjs/bull'; | ||||||
| import { Logger } from '@nestjs/common'; | import { Logger } from '@nestjs/common'; | ||||||
| import { InjectRepository } from '@nestjs/typeorm'; | import { InjectRepository } from '@nestjs/typeorm'; | ||||||
|   | |||||||
| @@ -1,14 +1,14 @@ | |||||||
| import { APP_UPLOAD_LOCATION } from '@app/common/constants'; | import { APP_UPLOAD_LOCATION } from '@app/common/constants'; | ||||||
| import { AssetEntity } from '@app/infra'; | import { AssetEntity } from '@app/infra'; | ||||||
| import { QueueName, JobName } from '@app/job'; | import { QueueName, JobName } from '@app/domain'; | ||||||
| import { IMp4ConversionProcessor } from '@app/job/interfaces/video-transcode.interface'; | import { IMp4ConversionProcessor } from '@app/domain'; | ||||||
| import { Process, Processor } from '@nestjs/bull'; | import { Process, Processor } from '@nestjs/bull'; | ||||||
| import { Logger } from '@nestjs/common'; | import { Logger } from '@nestjs/common'; | ||||||
| import { InjectRepository } from '@nestjs/typeorm'; | import { InjectRepository } from '@nestjs/typeorm'; | ||||||
| import { Job } from 'bull'; | import { Job } from 'bull'; | ||||||
| import ffmpeg from 'fluent-ffmpeg'; | import ffmpeg from 'fluent-ffmpeg'; | ||||||
| import { existsSync, mkdirSync } from 'fs'; | import { existsSync, mkdirSync } from 'fs'; | ||||||
| import { ImmichConfigService } from 'libs/immich-config/src'; | import { SystemConfigService } from '@app/domain'; | ||||||
| import { Repository } from 'typeorm'; | import { Repository } from 'typeorm'; | ||||||
|  |  | ||||||
| @Processor(QueueName.VIDEO_CONVERSION) | @Processor(QueueName.VIDEO_CONVERSION) | ||||||
| @@ -16,7 +16,7 @@ export class VideoTranscodeProcessor { | |||||||
|   constructor( |   constructor( | ||||||
|     @InjectRepository(AssetEntity) |     @InjectRepository(AssetEntity) | ||||||
|     private assetRepository: Repository<AssetEntity>, |     private assetRepository: Repository<AssetEntity>, | ||||||
|     private immichConfigService: ImmichConfigService, |     private systemConfigService: SystemConfigService, | ||||||
|   ) {} |   ) {} | ||||||
|  |  | ||||||
|   @Process({ name: JobName.MP4_CONVERSION, concurrency: 2 }) |   @Process({ name: JobName.MP4_CONVERSION, concurrency: 2 }) | ||||||
| @@ -41,7 +41,7 @@ export class VideoTranscodeProcessor { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   async runFFMPEGPipeLine(asset: AssetEntity, savedEncodedPath: string): Promise<void> { |   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) => { |     return new Promise((resolve, reject) => { | ||||||
|       ffmpeg(asset.originalPath) |       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": { |     "/user": { | ||||||
|       "get": { |       "get": { | ||||||
|         "operationId": "getAllUsers", |         "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": { |   "info": { | ||||||
| @@ -2882,6 +2882,181 @@ | |||||||
|           "name" |           "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": { |       "UserResponseDto": { | ||||||
|         "type": "object", |         "type": "object", | ||||||
|         "properties": { |         "properties": { | ||||||
| @@ -4476,181 +4651,6 @@ | |||||||
|         "required": [ |         "required": [ | ||||||
|           "command" |           "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 './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'; | 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 { BadRequestException, UnauthorizedException } from '@nestjs/common'; | ||||||
| import { authStub, entityStub, newCryptoRepositoryMock, newKeyRepositoryMock } from '../../test'; | import { authStub, entityStub, newCryptoRepositoryMock, newKeyRepositoryMock } from '../../test'; | ||||||
| import { ICryptoRepository } from '../auth'; | 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 { BadRequestException, Inject, Injectable, UnauthorizedException } from '@nestjs/common'; | ||||||
| import { AuthUserDto, ICryptoRepository } from '../auth'; | import { AuthUserDto, ICryptoRepository } from '../auth'; | ||||||
| import { IKeyRepository } from './api-key.repository'; | 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'; | import { ApiProperty } from '@nestjs/swagger'; | ||||||
|  |  | ||||||
| export class APIKeyResponseDto { | export class APIKeyResponseDto { | ||||||
|   | |||||||
| @@ -1,11 +1,23 @@ | |||||||
| import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common'; | import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common'; | ||||||
| import { APIKeyService } from './api-key'; | import { APIKeyService } from './api-key'; | ||||||
|  | import { SystemConfigService } from './system-config'; | ||||||
| import { UserService } from './user'; | import { UserService } from './user'; | ||||||
|  |  | ||||||
|  | export const INITIAL_SYSTEM_CONFIG = 'INITIAL_SYSTEM_CONFIG'; | ||||||
|  |  | ||||||
| const providers: Provider[] = [ | const providers: Provider[] = [ | ||||||
|   // |   // | ||||||
|   APIKeyService, |   APIKeyService, | ||||||
|  |   SystemConfigService, | ||||||
|   UserService, |   UserService, | ||||||
|  |  | ||||||
|  |   { | ||||||
|  |     provide: INITIAL_SYSTEM_CONFIG, | ||||||
|  |     inject: [SystemConfigService], | ||||||
|  |     useFactory: async (configService: SystemConfigService) => { | ||||||
|  |       return configService.getConfig(); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| @Global() | @Global() | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| export * from './api-key'; | export * from './api-key'; | ||||||
| export * from './auth'; | export * from './auth'; | ||||||
| export * from './domain.module'; | export * from './domain.module'; | ||||||
|  | export * from './job'; | ||||||
|  | export * from './system-config'; | ||||||
| export * from './user'; | 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 { | 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 { | export interface IMachineLearningJob { | ||||||
|   /** |   /** | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { AssetEntity } from '@app/infra'; | import { AssetEntity } from '@app/infra/db/entities'; | ||||||
| 
 | 
 | ||||||
| export interface IExifExtractionProcessor { | export interface IExifExtractionProcessor { | ||||||
|   /** |   /** | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { AssetEntity } from '@app/infra'; | import { AssetEntity } from '@app/infra/db/entities'; | ||||||
| 
 | 
 | ||||||
| export interface JpegGeneratorProcessor { | export interface JpegGeneratorProcessor { | ||||||
|   /** |   /** | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { UserEntity } from '@app/infra'; | import { UserEntity } from '@app/infra/db/entities'; | ||||||
| 
 | 
 | ||||||
| export interface IUserDeletionJob { | export interface IUserDeletionJob { | ||||||
|   /** |   /** | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { AssetEntity } from '@app/infra'; | import { AssetEntity } from '@app/infra/db/entities'; | ||||||
| 
 | 
 | ||||||
| export interface IMp4ConversionProcessor { | 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 { | export enum JobName { | ||||||
|   ASSET_UPLOADED = 'asset-uploaded', |   ASSET_UPLOADED = 'asset-uploaded', | ||||||
|   MP4_CONVERSION = 'mp4-conversion', |   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 { ValidateNested } from 'class-validator'; | ||||||
| import { SystemConfigFFmpegDto } from './system-config-ffmpeg.dto'; | import { SystemConfigFFmpegDto } from './system-config-ffmpeg.dto'; | ||||||
| import { SystemConfigOAuthDto } from './system-config-oauth.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 { BadRequestException, Injectable, Logger } from '@nestjs/common'; | ||||||
| import { InjectRepository } from '@nestjs/typeorm'; |  | ||||||
| import * as _ from 'lodash'; | import * as _ from 'lodash'; | ||||||
| import { Subject } from 'rxjs'; | 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>; | export type SystemConfigValidator = (config: SystemConfig) => void | Promise<void>; | ||||||
| 
 | 
 | ||||||
| @@ -37,16 +37,13 @@ const defaults: SystemConfig = Object.freeze({ | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| @Injectable() | @Injectable() | ||||||
| export class ImmichConfigService { | export class SystemConfigCore { | ||||||
|   private logger = new Logger(ImmichConfigService.name); |   private logger = new Logger(SystemConfigCore.name); | ||||||
|   private validators: SystemConfigValidator[] = []; |   private validators: SystemConfigValidator[] = []; | ||||||
| 
 | 
 | ||||||
|   public config$ = new Subject<SystemConfig>(); |   public config$ = new Subject<SystemConfig>(); | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor(private repository: ISystemConfigRepository) {} | ||||||
|     @InjectRepository(SystemConfigEntity) |  | ||||||
|     private systemConfigRepository: Repository<SystemConfigEntity>, |  | ||||||
|   ) {} |  | ||||||
| 
 | 
 | ||||||
|   public getDefaults(): SystemConfig { |   public getDefaults(): SystemConfig { | ||||||
|     return defaults; |     return defaults; | ||||||
| @@ -57,7 +54,7 @@ export class ImmichConfigService { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public async getConfig() { |   public async getConfig() { | ||||||
|     const overrides = await this.systemConfigRepository.find(); |     const overrides = await this.repository.load(); | ||||||
|     const config: DeepPartial<SystemConfig> = {}; |     const config: DeepPartial<SystemConfig> = {}; | ||||||
|     for (const { key, value } of overrides) { |     for (const { key, value } of overrides) { | ||||||
|       // set via dot notation
 |       // set via dot notation
 | ||||||
| @@ -95,11 +92,11 @@ export class ImmichConfigService { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (updates.length > 0) { |     if (updates.length > 0) { | ||||||
|       await this.systemConfigRepository.save(updates); |       await this.repository.saveAll(updates); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (deletes.length > 0) { |     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(); |     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 { | export class UserResponseDto { | ||||||
|   id!: string; |   id!: string; | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { UserEntity } from '@app/infra'; | import { UserEntity } from '@app/infra/db/entities'; | ||||||
| import { | import { | ||||||
|   BadRequestException, |   BadRequestException, | ||||||
|   ForbiddenException, |   ForbiddenException, | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { UserEntity } from '@app/infra'; | import { UserEntity } from '@app/infra/db/entities'; | ||||||
|  |  | ||||||
| export interface UserListFilter { | export interface UserListFilter { | ||||||
|   excludeId?: string; |   excludeId?: string; | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { IUserRepository } from '@app/domain'; | import { IUserRepository } from './user.repository'; | ||||||
| import { UserEntity } from '@app/infra'; | import { UserEntity } from '@app/infra/db/entities'; | ||||||
| import { BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common'; | import { BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common'; | ||||||
| import { when } from 'jest-when'; | import { when } from 'jest-when'; | ||||||
| import { newUserRepositoryMock } from '../../test'; | 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'; | import { AuthUserDto } from '../src'; | ||||||
|  |  | ||||||
| export const authStub = { | export const authStub = { | ||||||
| @@ -42,3 +42,33 @@ export const entityStub = { | |||||||
|     tags: [], |     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 './api-key.repository.mock'; | ||||||
| export * from './crypto.repository.mock'; | export * from './crypto.repository.mock'; | ||||||
| export * from './fixtures'; | export * from './fixtures'; | ||||||
|  | export * from './job.repository.mock'; | ||||||
|  | export * from './system-config.repository.mock'; | ||||||
| export * from './user.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 { databaseConfig, UserEntity } from '@app/infra'; | ||||||
|  | import { BullModule } from '@nestjs/bull'; | ||||||
| import { Global, Module, Provider } from '@nestjs/common'; | import { Global, Module, Provider } from '@nestjs/common'; | ||||||
| import { TypeOrmModule } from '@nestjs/typeorm'; | import { TypeOrmModule } from '@nestjs/typeorm'; | ||||||
| import { cryptoRepository } from './auth/crypto.repository'; | import { cryptoRepository } from './auth/crypto.repository'; | ||||||
| import { APIKeyEntity, UserRepository } from './db'; | import { APIKeyEntity, SystemConfigEntity, UserRepository } from './db'; | ||||||
| import { APIKeyRepository } from './db/repository'; | import { APIKeyRepository } from './db/repository'; | ||||||
|  | import { SystemConfigRepository } from './db/repository/system-config.repository'; | ||||||
|  | import { JobRepository } from './job'; | ||||||
|  |  | ||||||
| const providers: Provider[] = [ | const providers: Provider[] = [ | ||||||
|   // |   // | ||||||
|   { provide: ICryptoRepository, useValue: cryptoRepository }, |   { provide: ICryptoRepository, useValue: cryptoRepository }, | ||||||
|   { provide: IKeyRepository, useClass: APIKeyRepository }, |   { provide: IKeyRepository, useClass: APIKeyRepository }, | ||||||
|  |   { provide: IJobRepository, useClass: JobRepository }, | ||||||
|  |   { provide: ISystemConfigRepository, useClass: SystemConfigRepository }, | ||||||
|   { provide: IUserRepository, useClass: UserRepository }, |   { provide: IUserRepository, useClass: UserRepository }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| @Global() | @Global() | ||||||
| @Module({ | @Module({ | ||||||
|   imports: [ |   imports: [ | ||||||
|     // |  | ||||||
|     TypeOrmModule.forRoot(databaseConfig), |     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], |   providers: [...providers], | ||||||
|   exports: [...providers], |   exports: [...providers, BullModule], | ||||||
| }) | }) | ||||||
| export class InfraModule {} | 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 { AssetEntity } from '@app/infra'; | ||||||
| import { ImmichConfigModule } from '@app/immich-config'; |  | ||||||
| import { Module } from '@nestjs/common'; | import { Module } from '@nestjs/common'; | ||||||
| import { TypeOrmModule } from '@nestjs/typeorm'; | import { TypeOrmModule } from '@nestjs/typeorm'; | ||||||
| import { StorageService } from './storage.service'; | import { StorageService } from './storage.service'; | ||||||
|  |  | ||||||
| @Module({ | @Module({ | ||||||
|   imports: [TypeOrmModule.forFeature([AssetEntity, SystemConfigEntity]), ImmichConfigModule], |   imports: [TypeOrmModule.forFeature([AssetEntity])], | ||||||
|   providers: [StorageService], |   providers: [StorageService], | ||||||
|   exports: [StorageService], |   exports: [StorageService], | ||||||
| }) | }) | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { APP_UPLOAD_LOCATION } from '@app/common'; | import { APP_UPLOAD_LOCATION } from '@app/common'; | ||||||
| import { AssetEntity, AssetType, SystemConfig } from '@app/infra'; | 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 { Inject, Injectable, Logger } from '@nestjs/common'; | ||||||
| import { InjectRepository } from '@nestjs/typeorm'; | import { InjectRepository } from '@nestjs/typeorm'; | ||||||
| import fsPromise from 'fs/promises'; | import fsPromise from 'fs/promises'; | ||||||
| @@ -19,7 +19,7 @@ import { | |||||||
|   supportedMonthTokens, |   supportedMonthTokens, | ||||||
|   supportedSecondTokens, |   supportedSecondTokens, | ||||||
|   supportedYearTokens, |   supportedYearTokens, | ||||||
| } from './constants/supported-datetime-template'; | } from '@app/domain'; | ||||||
|  |  | ||||||
| const moveFile = promisify<string, string, mv.Options>(mv); | const moveFile = promisify<string, string, mv.Options>(mv); | ||||||
|  |  | ||||||
| @@ -32,14 +32,14 @@ export class StorageService { | |||||||
|   constructor( |   constructor( | ||||||
|     @InjectRepository(AssetEntity) |     @InjectRepository(AssetEntity) | ||||||
|     private assetRepository: Repository<AssetEntity>, |     private assetRepository: Repository<AssetEntity>, | ||||||
|     private immichConfigService: ImmichConfigService, |     private systemConfigService: SystemConfigService, | ||||||
|     @Inject(INITIAL_SYSTEM_CONFIG) config: SystemConfig, |     @Inject(INITIAL_SYSTEM_CONFIG) config: SystemConfig, | ||||||
|   ) { |   ) { | ||||||
|     this.storageTemplate = this.compile(config.storageTemplate.template); |     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.logger.debug(`Received new config, recompiling storage template: ${config.storageTemplate.template}`); | ||||||
|       this.storageTemplate = this.compile(config.storageTemplate.template); |       this.storageTemplate = this.compile(config.storageTemplate.template); | ||||||
|     }); |     }); | ||||||
|   | |||||||
| @@ -136,7 +136,8 @@ | |||||||
|       "^.+\\.(t|j)s$": "ts-jest" |       "^.+\\.(t|j)s$": "ts-jest" | ||||||
|     }, |     }, | ||||||
|     "collectCoverageFrom": [ |     "collectCoverageFrom": [ | ||||||
|       "**/*.(t|j)s" |       "**/*.(t|j)s", | ||||||
|  |       "!**/migrations/*" | ||||||
|     ], |     ], | ||||||
|     "coverageDirectory": "./coverage", |     "coverageDirectory": "./coverage", | ||||||
|     "coverageThreshold": { |     "coverageThreshold": { | ||||||
| @@ -145,10 +146,10 @@ | |||||||
|         "statements": 20 |         "statements": 20 | ||||||
|       }, |       }, | ||||||
|       "./libs/domain/": { |       "./libs/domain/": { | ||||||
|         "branches": 70, |         "branches": 75, | ||||||
|         "functions": 85, |         "functions": 85, | ||||||
|         "lines": 85, |         "lines": 90, | ||||||
|         "statements": 85 |         "statements": 90 | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "testEnvironment": "node", |     "testEnvironment": "node", | ||||||
| @@ -158,9 +159,6 @@ | |||||||
|     ], |     ], | ||||||
|     "moduleNameMapper": { |     "moduleNameMapper": { | ||||||
|       "@app/common": "<rootDir>/libs/common/src", |       "@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/storage(|/.*)$": "<rootDir>/libs/storage/src/$1", | ||||||
|       "^@app/infra(|/.*)$": "<rootDir>/libs/infra/src/$1", |       "^@app/infra(|/.*)$": "<rootDir>/libs/infra/src/$1", | ||||||
|       "^@app/domain(|/.*)$": "<rootDir>/libs/domain/src/$1" |       "^@app/domain(|/.*)$": "<rootDir>/libs/domain/src/$1" | ||||||
|   | |||||||
| @@ -18,10 +18,6 @@ | |||||||
|     "paths": { |     "paths": { | ||||||
|       "@app/common": ["libs/common/src"], |       "@app/common": ["libs/common/src"], | ||||||
|       "@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/storage/*": ["libs/storage/src/*"], |       "@app/storage/*": ["libs/storage/src/*"], | ||||||
|       "@app/infra": ["libs/infra/src"], |       "@app/infra": ["libs/infra/src"], | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user