diff --git a/docker/.env.test b/docker/.env.test index 582743d820..d48b4f53fa 100644 --- a/docker/.env.test +++ b/docker/.env.test @@ -5,7 +5,7 @@ DB_PASSWORD=postgres DB_DATABASE_NAME=e2e_test # Redis -REDIS_HOSTNAME=immich_redis_test +REDIS_HOSTNAME=immich-redis-test # Upload File Config UPLOAD_LOCATION=./upload diff --git a/server/apps/immich/src/api-v1/asset/asset.module.ts b/server/apps/immich/src/api-v1/asset/asset.module.ts index 12fe50e588..f03f89c07a 100644 --- a/server/apps/immich/src/api-v1/asset/asset.module.ts +++ b/server/apps/immich/src/api-v1/asset/asset.module.ts @@ -3,7 +3,6 @@ import { AssetService } from './asset.service'; import { AssetController } from './asset.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AssetEntity } from '@app/infra'; -import { BullModule } from '@nestjs/bull'; import { BackgroundTaskModule } from '../../modules/background-task/background-task.module'; import { BackgroundTaskService } from '../../modules/background-task/background-task.service'; import { CommunicationModule } from '../communication/communication.module'; @@ -12,7 +11,6 @@ import { DownloadModule } from '../../modules/download/download.module'; import { TagModule } from '../tag/tag.module'; import { AlbumModule } from '../album/album.module'; import { StorageModule } from '@app/storage'; -import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.constant'; import { ShareModule } from '../share/share.module'; const ASSET_REPOSITORY_PROVIDER = { @@ -29,7 +27,6 @@ const ASSET_REPOSITORY_PROVIDER = { TagModule, StorageModule, forwardRef(() => AlbumModule), - BullModule.registerQueue(...immichSharedQueues), ShareModule, ], controllers: [AssetController], diff --git a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts index 21dd774c6f..02b4511c22 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts @@ -9,7 +9,7 @@ import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto'; import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto'; import { DownloadService } from '../../modules/download/download.service'; import { BackgroundTaskService } from '../../modules/background-task/background-task.service'; -import { IAssetUploadedJob, IVideoTranscodeJob } from '@app/job'; +import { IAssetUploadedJob, IVideoTranscodeJob } from '@app/domain'; import { Queue } from 'bull'; import { IAlbumRepository } from '../album/album-repository'; import { StorageService } from '@app/storage'; diff --git a/server/apps/immich/src/api-v1/asset/asset.service.ts b/server/apps/immich/src/api-v1/asset/asset.service.ts index 0cfdc759bc..93ceeb66f1 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -43,7 +43,7 @@ import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-as import { UpdateAssetDto } from './dto/update-asset.dto'; import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto'; import { BackgroundTaskService } from '../../modules/background-task/background-task.service'; -import { IAssetUploadedJob, IVideoTranscodeJob, QueueName, JobName } from '@app/job'; +import { IAssetUploadedJob, IVideoTranscodeJob, QueueName, JobName } from '@app/domain'; import { InjectQueue } from '@nestjs/bull'; import { Queue } from 'bull'; import { DownloadService } from '../../modules/download/download.service'; diff --git a/server/apps/immich/src/api-v1/auth/auth.module.ts b/server/apps/immich/src/api-v1/auth/auth.module.ts index 5aeeb31590..1affdbf656 100644 --- a/server/apps/immich/src/api-v1/auth/auth.module.ts +++ b/server/apps/immich/src/api-v1/auth/auth.module.ts @@ -1,12 +1,11 @@ import { Module } from '@nestjs/common'; -import { ImmichConfigModule } from '@app/immich-config'; import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module'; import { OAuthModule } from '../oauth/oauth.module'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; @Module({ - imports: [ImmichJwtModule, OAuthModule, ImmichConfigModule], + imports: [ImmichJwtModule, OAuthModule], controllers: [AuthController], providers: [AuthService], }) diff --git a/server/apps/immich/src/api-v1/auth/auth.service.spec.ts b/server/apps/immich/src/api-v1/auth/auth.service.spec.ts index 041daa2c5a..3c43e243b8 100644 --- a/server/apps/immich/src/api-v1/auth/auth.service.spec.ts +++ b/server/apps/immich/src/api-v1/auth/auth.service.spec.ts @@ -2,7 +2,7 @@ import { UserEntity } from '@app/infra'; import { BadRequestException, UnauthorizedException } from '@nestjs/common'; import * as bcrypt from 'bcrypt'; import { SystemConfig } from '@app/infra'; -import { ImmichConfigService } from '@app/immich-config'; +import { SystemConfigService } from '@app/domain'; import { AuthType } from '../../constants/jwt.constant'; import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service'; import { OAuthService } from '../oauth/oauth.service'; @@ -50,7 +50,7 @@ describe('AuthService', () => { let sut: AuthService; let userRepositoryMock: jest.Mocked; let immichJwtServiceMock: jest.Mocked; - let immichConfigServiceMock: jest.Mocked; + let immichConfigServiceMock: jest.Mocked; let oauthServiceMock: jest.Mocked; let compare: jest.Mock; @@ -89,7 +89,7 @@ describe('AuthService', () => { immichConfigServiceMock = { config$: { subscribe: jest.fn() }, - } as unknown as jest.Mocked; + } as unknown as jest.Mocked; sut = new AuthService( oauthServiceMock, diff --git a/server/apps/immich/src/api-v1/auth/auth.service.ts b/server/apps/immich/src/api-v1/auth/auth.service.ts index 33773877dc..7f36a0372d 100644 --- a/server/apps/immich/src/api-v1/auth/auth.service.ts +++ b/server/apps/immich/src/api-v1/auth/auth.service.ts @@ -20,7 +20,7 @@ import { LoginResponseDto } from './response-dto/login-response.dto'; import { LogoutResponseDto } from './response-dto/logout-response.dto'; import { OAuthService } from '../oauth/oauth.service'; import { UserCore } from '@app/domain'; -import { ImmichConfigService, INITIAL_SYSTEM_CONFIG } from '@app/immich-config'; +import { SystemConfigService, INITIAL_SYSTEM_CONFIG } from '@app/domain'; import { SystemConfig } from '@app/infra'; @Injectable() @@ -32,7 +32,7 @@ export class AuthService { private oauthService: OAuthService, private immichJwtService: ImmichJwtService, @Inject(IUserRepository) userRepository: IUserRepository, - private configService: ImmichConfigService, + private configService: SystemConfigService, @Inject(INITIAL_SYSTEM_CONFIG) private config: SystemConfig, ) { this.userCore = new UserCore(userRepository); diff --git a/server/apps/immich/src/api-v1/job/job.module.ts b/server/apps/immich/src/api-v1/job/job.module.ts index 1bd242fdcc..96ade41e32 100644 --- a/server/apps/immich/src/api-v1/job/job.module.ts +++ b/server/apps/immich/src/api-v1/job/job.module.ts @@ -6,20 +6,10 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { ExifEntity } from '@app/infra'; import { TagModule } from '../tag/tag.module'; import { AssetModule } from '../asset/asset.module'; - import { StorageModule } from '@app/storage'; -import { BullModule } from '@nestjs/bull'; -import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.constant'; @Module({ - imports: [ - TypeOrmModule.forFeature([ExifEntity]), - ImmichJwtModule, - TagModule, - AssetModule, - StorageModule, - BullModule.registerQueue(...immichSharedQueues), - ], + imports: [TypeOrmModule.forFeature([ExifEntity]), ImmichJwtModule, TagModule, AssetModule, StorageModule], controllers: [JobController], providers: [JobService], }) diff --git a/server/apps/immich/src/api-v1/job/job.service.ts b/server/apps/immich/src/api-v1/job/job.service.ts index 0326357a0c..519c527fd3 100644 --- a/server/apps/immich/src/api-v1/job/job.service.ts +++ b/server/apps/immich/src/api-v1/job/job.service.ts @@ -1,4 +1,11 @@ -import { IMetadataExtractionJob, IThumbnailGenerationJob, IVideoTranscodeJob, QueueName, JobName } from '@app/job'; +import { + IMachineLearningJob, + IMetadataExtractionJob, + IThumbnailGenerationJob, + IVideoTranscodeJob, + QueueName, + JobName, +} from '@app/domain'; import { InjectQueue } from '@nestjs/bull'; import { Queue } from 'bull'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; @@ -7,7 +14,6 @@ import { IAssetRepository } from '../asset/asset-repository'; import { AssetType } from '@app/infra'; import { GetJobDto, JobId } from './dto/get-job.dto'; import { JobStatusResponseDto } from './response-dto/job-status-response.dto'; -import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface'; import { StorageService } from '@app/storage'; import { MACHINE_LEARNING_ENABLED } from '@app/common'; diff --git a/server/apps/immich/src/api-v1/oauth/oauth.module.ts b/server/apps/immich/src/api-v1/oauth/oauth.module.ts index ef703b7a6e..093657c893 100644 --- a/server/apps/immich/src/api-v1/oauth/oauth.module.ts +++ b/server/apps/immich/src/api-v1/oauth/oauth.module.ts @@ -1,11 +1,10 @@ -import { ImmichConfigModule } from '@app/immich-config'; import { Module } from '@nestjs/common'; import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module'; import { OAuthController } from './oauth.controller'; import { OAuthService } from './oauth.service'; @Module({ - imports: [ImmichJwtModule, ImmichConfigModule], + imports: [ImmichJwtModule], controllers: [OAuthController], providers: [OAuthService], exports: [OAuthService], diff --git a/server/apps/immich/src/api-v1/oauth/oauth.service.spec.ts b/server/apps/immich/src/api-v1/oauth/oauth.service.spec.ts index 2dc60d3f3f..92ec9cc751 100644 --- a/server/apps/immich/src/api-v1/oauth/oauth.service.spec.ts +++ b/server/apps/immich/src/api-v1/oauth/oauth.service.spec.ts @@ -1,5 +1,5 @@ import { SystemConfig, UserEntity } from '@app/infra'; -import { ImmichConfigService } from '@app/immich-config'; +import { SystemConfigService } from '@app/domain'; import { BadRequestException } from '@nestjs/common'; import { generators, Issuer } from 'openid-client'; import { AuthUserDto } from '../../decorators/auth-user.decorator'; @@ -86,7 +86,7 @@ jest.mock('@nestjs/common', () => ({ describe('OAuthService', () => { let sut: OAuthService; let userRepositoryMock: jest.Mocked; - let immichConfigServiceMock: jest.Mocked; + let immichConfigServiceMock: jest.Mocked; let immichJwtServiceMock: jest.Mocked; let callbackMock: jest.Mock; @@ -132,7 +132,7 @@ describe('OAuthService', () => { immichConfigServiceMock = { config$: { subscribe: jest.fn() }, - } as unknown as jest.Mocked; + } as unknown as jest.Mocked; sut = new OAuthService(immichJwtServiceMock, immichConfigServiceMock, userRepositoryMock, config.disabled); }); diff --git a/server/apps/immich/src/api-v1/oauth/oauth.service.ts b/server/apps/immich/src/api-v1/oauth/oauth.service.ts index 5f65556cac..5096d5b712 100644 --- a/server/apps/immich/src/api-v1/oauth/oauth.service.ts +++ b/server/apps/immich/src/api-v1/oauth/oauth.service.ts @@ -1,11 +1,10 @@ import { SystemConfig } from '@app/infra'; -import { ImmichConfigService, INITIAL_SYSTEM_CONFIG } from '@app/immich-config'; import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'; import { ClientMetadata, custom, generators, Issuer, UserinfoResponse } from 'openid-client'; import { AuthUserDto } from '../../decorators/auth-user.decorator'; import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service'; import { LoginResponseDto } from '../auth/response-dto/login-response.dto'; -import { IUserRepository, UserResponseDto, UserCore } from '@app/domain'; +import { IUserRepository, UserResponseDto, UserCore, SystemConfigService, INITIAL_SYSTEM_CONFIG } from '@app/domain'; import { OAuthCallbackDto } from './dto/oauth-auth-code.dto'; import { OAuthConfigDto } from './dto/oauth-config.dto'; import { OAuthConfigResponseDto } from './response-dto/oauth-config-response.dto'; @@ -23,7 +22,7 @@ export class OAuthService { constructor( private immichJwtService: ImmichJwtService, - immichConfigService: ImmichConfigService, + configService: SystemConfigService, @Inject(IUserRepository) userRepository: IUserRepository, @Inject(INITIAL_SYSTEM_CONFIG) private config: SystemConfig, ) { @@ -33,7 +32,7 @@ export class OAuthService { timeout: 30000, }); - immichConfigService.config$.subscribe((config) => (this.config = config)); + configService.config$.subscribe((config) => (this.config = config)); } public async generateConfig(dto: OAuthConfigDto): Promise { diff --git a/server/apps/immich/src/api-v1/system-config/system-config.module.ts b/server/apps/immich/src/api-v1/system-config/system-config.module.ts deleted file mode 100644 index 20ae0355d9..0000000000 --- a/server/apps/immich/src/api-v1/system-config/system-config.module.ts +++ /dev/null @@ -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 {} diff --git a/server/apps/immich/src/api-v1/system-config/system-config.service.ts b/server/apps/immich/src/api-v1/system-config/system-config.service.ts deleted file mode 100644 index d3b6fb10e9..0000000000 --- a/server/apps/immich/src/api-v1/system-config/system-config.service.ts +++ /dev/null @@ -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 { - 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 { - 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; - } -} diff --git a/server/apps/immich/src/app.controller.ts b/server/apps/immich/src/app.controller.ts index 44207a609d..04c6aaeb26 100644 --- a/server/apps/immich/src/app.controller.ts +++ b/server/apps/immich/src/app.controller.ts @@ -1,10 +1,10 @@ import { Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'; import { ApiExcludeEndpoint } from '@nestjs/swagger'; -import { ImmichConfigService } from '@app/immich-config'; +import { SystemConfigService } from '@app/domain'; @Controller() export class AppController { - constructor(private configService: ImmichConfigService) {} + constructor(private configService: SystemConfigService) {} @ApiExcludeEndpoint() @Post('refresh-config') diff --git a/server/apps/immich/src/app.module.ts b/server/apps/immich/src/app.module.ts index be3a10da43..b54ed270cc 100644 --- a/server/apps/immich/src/app.module.ts +++ b/server/apps/immich/src/app.module.ts @@ -1,11 +1,10 @@ -import { immichAppConfig, immichBullAsyncConfig } from '@app/common/config'; +import { immichAppConfig } from '@app/common/config'; import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { AssetModule } from './api-v1/asset/asset.module'; import { AuthModule } from './api-v1/auth/auth.module'; import { ImmichJwtModule } from './modules/immich-jwt/immich-jwt.module'; import { DeviceInfoModule } from './api-v1/device-info/device-info.module'; import { ConfigModule } from '@nestjs/config'; -import { BullModule } from '@nestjs/bull'; import { ServerInfoModule } from './api-v1/server-info/server-info.module'; import { BackgroundTaskModule } from './modules/background-task/background-task.module'; import { CommunicationModule } from './api-v1/communication/communication.module'; @@ -14,14 +13,12 @@ import { AppController } from './app.controller'; import { ScheduleModule } from '@nestjs/schedule'; import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module'; import { JobModule } from './api-v1/job/job.module'; -import { SystemConfigModule } from './api-v1/system-config/system-config.module'; import { OAuthModule } from './api-v1/oauth/oauth.module'; import { TagModule } from './api-v1/tag/tag.module'; -import { ImmichConfigModule } from '@app/immich-config'; import { ShareModule } from './api-v1/share/share.module'; import { DomainModule } from '@app/domain'; import { InfraModule } from '@app/infra'; -import { APIKeyController, UserController } from './controllers'; +import { APIKeyController, SystemConfigController, UserController } from './controllers'; @Module({ imports: [ @@ -37,12 +34,9 @@ import { APIKeyController, UserController } from './controllers'; OAuthModule, ImmichJwtModule, - ImmichConfigModule, DeviceInfoModule, - BullModule.forRootAsync(immichBullAsyncConfig), - ServerInfoModule, BackgroundTaskModule, @@ -57,8 +51,6 @@ import { APIKeyController, UserController } from './controllers'; JobModule, - SystemConfigModule, - TagModule, ShareModule, @@ -67,6 +59,7 @@ import { APIKeyController, UserController } from './controllers'; // AppController, APIKeyController, + SystemConfigController, UserController, ], providers: [], diff --git a/server/apps/immich/src/controllers/index.ts b/server/apps/immich/src/controllers/index.ts index bac062eb90..545d8ba79f 100644 --- a/server/apps/immich/src/controllers/index.ts +++ b/server/apps/immich/src/controllers/index.ts @@ -1,2 +1,3 @@ export * from './api-key.controller'; +export * from './system-config.controller'; export * from './user.controller'; diff --git a/server/apps/immich/src/api-v1/system-config/system-config.controller.ts b/server/apps/immich/src/controllers/system-config.controller.ts similarity index 75% rename from server/apps/immich/src/api-v1/system-config/system-config.controller.ts rename to server/apps/immich/src/controllers/system-config.controller.ts index ed247b382e..d815683258 100644 --- a/server/apps/immich/src/api-v1/system-config/system-config.controller.ts +++ b/server/apps/immich/src/controllers/system-config.controller.ts @@ -1,9 +1,7 @@ +import { SystemConfigDto, SystemConfigService, SystemConfigTemplateStorageOptionDto } from '@app/domain'; import { Body, Controller, Get, Put, ValidationPipe } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { Authenticated } from '../../decorators/authenticated.decorator'; -import { SystemConfigTemplateStorageOptionDto } from './response-dto/system-config-template-storage-option.dto'; -import { SystemConfigDto } from './dto/system-config.dto'; -import { SystemConfigService } from './system-config.service'; +import { Authenticated } from '../decorators/authenticated.decorator'; @ApiTags('System Config') @ApiBearerAuth() diff --git a/server/apps/immich/src/modules/background-task/background-task.module.ts b/server/apps/immich/src/modules/background-task/background-task.module.ts index 2e557fff7e..fc199abc54 100644 --- a/server/apps/immich/src/modules/background-task/background-task.module.ts +++ b/server/apps/immich/src/modules/background-task/background-task.module.ts @@ -1,6 +1,6 @@ import { BullModule } from '@nestjs/bull'; import { Module } from '@nestjs/common'; -import { QueueName } from '@app/job'; +import { QueueName } from '@app/domain'; import { BackgroundTaskProcessor } from './background-task.processor'; import { BackgroundTaskService } from './background-task.service'; diff --git a/server/apps/immich/src/modules/background-task/background-task.processor.ts b/server/apps/immich/src/modules/background-task/background-task.processor.ts index 9607bbe5ba..7e68185689 100644 --- a/server/apps/immich/src/modules/background-task/background-task.processor.ts +++ b/server/apps/immich/src/modules/background-task/background-task.processor.ts @@ -1,7 +1,7 @@ import { assetUtils } from '@app/common/utils'; import { Process, Processor } from '@nestjs/bull'; import { Job } from 'bull'; -import { JobName, QueueName } from '@app/job'; +import { JobName, QueueName } from '@app/domain'; import { AssetResponseDto } from '../../api-v1/asset/response-dto/asset-response.dto'; @Processor(QueueName.BACKGROUND_TASK) diff --git a/server/apps/immich/src/modules/background-task/background-task.service.ts b/server/apps/immich/src/modules/background-task/background-task.service.ts index dd9a32a9be..a7124c8807 100644 --- a/server/apps/immich/src/modules/background-task/background-task.service.ts +++ b/server/apps/immich/src/modules/background-task/background-task.service.ts @@ -1,7 +1,7 @@ import { InjectQueue } from '@nestjs/bull/dist/decorators'; import { Injectable } from '@nestjs/common'; import { Queue } from 'bull'; -import { JobName, QueueName } from '@app/job'; +import { JobName, QueueName } from '@app/domain'; import { AssetResponseDto } from '../../api-v1/asset/response-dto/asset-response.dto'; @Injectable() diff --git a/server/apps/immich/src/modules/schedule-tasks/schedule-tasks.module.ts b/server/apps/immich/src/modules/schedule-tasks/schedule-tasks.module.ts index 55b55a3785..9d511c6192 100644 --- a/server/apps/immich/src/modules/schedule-tasks/schedule-tasks.module.ts +++ b/server/apps/immich/src/modules/schedule-tasks/schedule-tasks.module.ts @@ -1,15 +1,10 @@ -import { BullModule } from '@nestjs/bull'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AssetEntity, ExifEntity, UserEntity } from '@app/infra'; import { ScheduleTasksService } from './schedule-tasks.service'; -import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.constant'; @Module({ - imports: [ - TypeOrmModule.forFeature([AssetEntity, ExifEntity, UserEntity]), - BullModule.registerQueue(...immichSharedQueues), - ], + imports: [TypeOrmModule.forFeature([AssetEntity, ExifEntity, UserEntity])], providers: [ScheduleTasksService], }) export class ScheduleTasksModule {} diff --git a/server/apps/immich/src/modules/schedule-tasks/schedule-tasks.service.ts b/server/apps/immich/src/modules/schedule-tasks/schedule-tasks.service.ts index 764879bfc3..3cd565cc9d 100644 --- a/server/apps/immich/src/modules/schedule-tasks/schedule-tasks.service.ts +++ b/server/apps/immich/src/modules/schedule-tasks/schedule-tasks.service.ts @@ -5,9 +5,9 @@ import { IsNull, Not, Repository } from 'typeorm'; import { AssetEntity, AssetType, ExifEntity, UserEntity } from '@app/infra'; import { InjectQueue } from '@nestjs/bull'; import { Queue } from 'bull'; -import { IMetadataExtractionJob, IVideoTranscodeJob, QueueName, JobName } from '@app/job'; +import { IMetadataExtractionJob, IVideoTranscodeJob, QueueName, JobName } from '@app/domain'; import { ConfigService } from '@nestjs/config'; -import { IUserDeletionJob } from '@app/job/interfaces/user-deletion.interface'; +import { IUserDeletionJob } from '@app/domain'; import { userUtils } from '@app/common'; @Injectable() diff --git a/server/apps/immich/test/jest-e2e.json b/server/apps/immich/test/jest-e2e.json index cdfdc853fb..c0014051c5 100644 --- a/server/apps/immich/test/jest-e2e.json +++ b/server/apps/immich/test/jest-e2e.json @@ -8,8 +8,6 @@ }, "moduleNameMapper": { "^@app/common": "../../../libs/common/src", - "^@app/job(|/.*)$": "../../../libs/job/src/$1", - "^@app/immich-config(|/.*)$": "../../../libs/immich-config/src/$1", "^@app/storage(|/.*)$": "../../../libs/storage/src/$1", "^@app/infra(|/.*)$": "../../../libs/infra/src/$1", "^@app/domain(|/.*)$": "../../../libs/domain/src/$1" diff --git a/server/apps/microservices/src/microservices.module.ts b/server/apps/microservices/src/microservices.module.ts index ba447c656f..0e2f2064ea 100644 --- a/server/apps/microservices/src/microservices.module.ts +++ b/server/apps/microservices/src/microservices.module.ts @@ -1,11 +1,9 @@ -import { immichAppConfig, immichBullAsyncConfig } from '@app/common/config'; +import { immichAppConfig } from '@app/common/config'; import { AssetEntity, ExifEntity, SmartInfoEntity, UserEntity, APIKeyEntity, InfraModule } from '@app/infra'; import { StorageModule } from '@app/storage'; -import { BullModule } from '@nestjs/bull'; import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ImmichConfigModule } from 'libs/immich-config/src'; import { CommunicationModule } from '../../immich/src/api-v1/communication/communication.module'; import { MicroservicesService } from './microservices.service'; import { AssetUploadedProcessor } from './processors/asset-uploaded.processor'; @@ -16,7 +14,6 @@ import { StorageMigrationProcessor } from './processors/storage-migration.proces import { ThumbnailGeneratorProcessor } from './processors/thumbnail.processor'; import { UserDeletionProcessor } from './processors/user-deletion.processor'; import { VideoTranscodeProcessor } from './processors/video-transcode.processor'; -import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.constant'; import { DomainModule } from '@app/domain'; @Module({ @@ -25,11 +22,8 @@ import { DomainModule } from '@app/domain'; DomainModule.register({ imports: [InfraModule], }), - ImmichConfigModule, TypeOrmModule.forFeature([UserEntity, ExifEntity, AssetEntity, SmartInfoEntity, APIKeyEntity]), StorageModule, - BullModule.forRootAsync(immichBullAsyncConfig), - BullModule.registerQueue(...immichSharedQueues), CommunicationModule, ], controllers: [], @@ -44,6 +38,5 @@ import { DomainModule } from '@app/domain'; UserDeletionProcessor, StorageMigrationProcessor, ], - exports: [BullModule], }) export class MicroservicesModule {} diff --git a/server/apps/microservices/src/microservices.service.ts b/server/apps/microservices/src/microservices.service.ts index 4e06e454cb..329f2b5d4d 100644 --- a/server/apps/microservices/src/microservices.service.ts +++ b/server/apps/microservices/src/microservices.service.ts @@ -1,4 +1,4 @@ -import { QueueName } from '@app/job'; +import { QueueName } from '@app/domain'; import { InjectQueue } from '@nestjs/bull'; import { Injectable, OnModuleInit } from '@nestjs/common'; import { Queue } from 'bull'; diff --git a/server/apps/microservices/src/processors/asset-uploaded.processor.ts b/server/apps/microservices/src/processors/asset-uploaded.processor.ts index 55c4e8dcca..c7602f46c3 100644 --- a/server/apps/microservices/src/processors/asset-uploaded.processor.ts +++ b/server/apps/microservices/src/processors/asset-uploaded.processor.ts @@ -6,7 +6,7 @@ import { IVideoTranscodeJob, QueueName, JobName, -} from '@app/job'; +} from '@app/domain'; import { InjectQueue, Process, Processor } from '@nestjs/bull'; import { Job, Queue } from 'bull'; diff --git a/server/apps/microservices/src/processors/generate-checksum.processor.ts b/server/apps/microservices/src/processors/generate-checksum.processor.ts index 5885477723..32c2035151 100644 --- a/server/apps/microservices/src/processors/generate-checksum.processor.ts +++ b/server/apps/microservices/src/processors/generate-checksum.processor.ts @@ -1,5 +1,5 @@ import { AssetEntity } from '@app/infra'; -import { QueueName } from '@app/job'; +import { QueueName } from '@app/domain'; import { Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; diff --git a/server/apps/microservices/src/processors/machine-learning.processor.ts b/server/apps/microservices/src/processors/machine-learning.processor.ts index 8c2a28f31a..d4e25af205 100644 --- a/server/apps/microservices/src/processors/machine-learning.processor.ts +++ b/server/apps/microservices/src/processors/machine-learning.processor.ts @@ -1,7 +1,7 @@ import { AssetEntity } from '@app/infra'; import { SmartInfoEntity } from '@app/infra'; -import { QueueName, JobName } from '@app/job'; -import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface'; +import { QueueName, JobName } from '@app/domain'; +import { IMachineLearningJob } from '@app/domain'; import { Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; diff --git a/server/apps/microservices/src/processors/metadata-extraction.processor.ts b/server/apps/microservices/src/processors/metadata-extraction.processor.ts index e9b42017de..d31069df0f 100644 --- a/server/apps/microservices/src/processors/metadata-extraction.processor.ts +++ b/server/apps/microservices/src/processors/metadata-extraction.processor.ts @@ -5,7 +5,7 @@ import { IVideoLengthExtractionProcessor, QueueName, JobName, -} from '@app/job'; +} from '@app/domain'; import { Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; diff --git a/server/apps/microservices/src/processors/storage-migration.processor.ts b/server/apps/microservices/src/processors/storage-migration.processor.ts index e39a507366..ef5c9d87ef 100644 --- a/server/apps/microservices/src/processors/storage-migration.processor.ts +++ b/server/apps/microservices/src/processors/storage-migration.processor.ts @@ -1,7 +1,7 @@ import { APP_UPLOAD_LOCATION } from '@app/common'; import { AssetEntity } from '@app/infra'; -import { ImmichConfigService } from '@app/immich-config'; -import { QueueName, JobName } from '@app/job'; +import { SystemConfigService } from '@app/domain'; +import { QueueName, JobName } from '@app/domain'; import { StorageService } from '@app/storage'; import { Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; @@ -14,7 +14,7 @@ export class StorageMigrationProcessor { constructor( private storageService: StorageService, - private immichConfigService: ImmichConfigService, + private systemConfigService: SystemConfigService, @InjectRepository(AssetEntity) private assetRepository: Repository, @@ -56,6 +56,6 @@ export class StorageMigrationProcessor { */ @Process({ name: JobName.CONFIG_CHANGE, concurrency: 1 }) async updateTemplate() { - await this.immichConfigService.refreshConfig(); + await this.systemConfigService.refreshConfig(); } } diff --git a/server/apps/microservices/src/processors/thumbnail.processor.ts b/server/apps/microservices/src/processors/thumbnail.processor.ts index 9ee225755c..0d51981ba6 100644 --- a/server/apps/microservices/src/processors/thumbnail.processor.ts +++ b/server/apps/microservices/src/processors/thumbnail.processor.ts @@ -1,6 +1,6 @@ import { APP_UPLOAD_LOCATION } from '@app/common'; import { AssetEntity, AssetType } from '@app/infra'; -import { WebpGeneratorProcessor, JpegGeneratorProcessor, QueueName, JobName } from '@app/job'; +import { WebpGeneratorProcessor, JpegGeneratorProcessor, QueueName, JobName } from '@app/domain'; import { InjectQueue, Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; @@ -13,7 +13,7 @@ import sharp from 'sharp'; import { Repository } from 'typeorm/repository/Repository'; import { join } from 'path'; import { CommunicationGateway } from 'apps/immich/src/api-v1/communication/communication.gateway'; -import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface'; +import { IMachineLearningJob } from '@app/domain'; @Processor(QueueName.THUMBNAIL_GENERATION) export class ThumbnailGeneratorProcessor { diff --git a/server/apps/microservices/src/processors/user-deletion.processor.ts b/server/apps/microservices/src/processors/user-deletion.processor.ts index 53f4451c69..6de6530304 100644 --- a/server/apps/microservices/src/processors/user-deletion.processor.ts +++ b/server/apps/microservices/src/processors/user-deletion.processor.ts @@ -1,7 +1,7 @@ import { APP_UPLOAD_LOCATION, userUtils } from '@app/common'; import { APIKeyEntity, AssetEntity, UserEntity } from '@app/infra'; -import { QueueName, JobName } from '@app/job'; -import { IUserDeletionJob } from '@app/job/interfaces/user-deletion.interface'; +import { QueueName, JobName } from '@app/domain'; +import { IUserDeletionJob } from '@app/domain'; import { Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; diff --git a/server/apps/microservices/src/processors/video-transcode.processor.ts b/server/apps/microservices/src/processors/video-transcode.processor.ts index c7e297e1d9..170cb70de2 100644 --- a/server/apps/microservices/src/processors/video-transcode.processor.ts +++ b/server/apps/microservices/src/processors/video-transcode.processor.ts @@ -1,14 +1,14 @@ import { APP_UPLOAD_LOCATION } from '@app/common/constants'; import { AssetEntity } from '@app/infra'; -import { QueueName, JobName } from '@app/job'; -import { IMp4ConversionProcessor } from '@app/job/interfaces/video-transcode.interface'; +import { QueueName, JobName } from '@app/domain'; +import { IMp4ConversionProcessor } from '@app/domain'; import { Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Job } from 'bull'; import ffmpeg from 'fluent-ffmpeg'; import { existsSync, mkdirSync } from 'fs'; -import { ImmichConfigService } from 'libs/immich-config/src'; +import { SystemConfigService } from '@app/domain'; import { Repository } from 'typeorm'; @Processor(QueueName.VIDEO_CONVERSION) @@ -16,7 +16,7 @@ export class VideoTranscodeProcessor { constructor( @InjectRepository(AssetEntity) private assetRepository: Repository, - private immichConfigService: ImmichConfigService, + private systemConfigService: SystemConfigService, ) {} @Process({ name: JobName.MP4_CONVERSION, concurrency: 2 }) @@ -41,7 +41,7 @@ export class VideoTranscodeProcessor { } async runFFMPEGPipeLine(asset: AssetEntity, savedEncodedPath: string): Promise { - const config = await this.immichConfigService.getConfig(); + const config = await this.systemConfigService.getConfig(); return new Promise((resolve, reject) => { ffmpeg(asset.originalPath) diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index ec2158d7d1..c37159c9c1 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -148,6 +148,122 @@ ] } }, + "/system-config": { + "get": { + "operationId": "getConfig", + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemConfigDto" + } + } + } + } + }, + "tags": [ + "System Config" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "put": { + "operationId": "updateConfig", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemConfigDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemConfigDto" + } + } + } + } + }, + "tags": [ + "System Config" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/system-config/defaults": { + "get": { + "operationId": "getDefaults", + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemConfigDto" + } + } + } + } + }, + "tags": [ + "System Config" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/system-config/storage-template-options": { + "get": { + "operationId": "getStorageTemplateOptions", + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemConfigTemplateStorageOptionDto" + } + } + } + } + }, + "tags": [ + "System Config" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, "/user": { "get": { "operationId": "getAllUsers", @@ -2683,122 +2799,6 @@ } ] } - }, - "/system-config": { - "get": { - "operationId": "getConfig", - "description": "", - "parameters": [], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SystemConfigDto" - } - } - } - } - }, - "tags": [ - "System Config" - ], - "security": [ - { - "bearer": [] - } - ] - }, - "put": { - "operationId": "updateConfig", - "description": "", - "parameters": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SystemConfigDto" - } - } - } - }, - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SystemConfigDto" - } - } - } - } - }, - "tags": [ - "System Config" - ], - "security": [ - { - "bearer": [] - } - ] - } - }, - "/system-config/defaults": { - "get": { - "operationId": "getDefaults", - "description": "", - "parameters": [], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SystemConfigDto" - } - } - } - } - }, - "tags": [ - "System Config" - ], - "security": [ - { - "bearer": [] - } - ] - } - }, - "/system-config/storage-template-options": { - "get": { - "operationId": "getStorageTemplateOptions", - "description": "", - "parameters": [], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SystemConfigTemplateStorageOptionDto" - } - } - } - } - }, - "tags": [ - "System Config" - ], - "security": [ - { - "bearer": [] - } - ] - } } }, "info": { @@ -2882,6 +2882,181 @@ "name" ] }, + "SystemConfigFFmpegDto": { + "type": "object", + "properties": { + "crf": { + "type": "string" + }, + "preset": { + "type": "string" + }, + "targetVideoCodec": { + "type": "string" + }, + "targetAudioCodec": { + "type": "string" + }, + "targetScaling": { + "type": "string" + } + }, + "required": [ + "crf", + "preset", + "targetVideoCodec", + "targetAudioCodec", + "targetScaling" + ] + }, + "SystemConfigOAuthDto": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "issuerUrl": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "clientSecret": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "buttonText": { + "type": "string" + }, + "autoRegister": { + "type": "boolean" + }, + "autoLaunch": { + "type": "boolean" + }, + "mobileOverrideEnabled": { + "type": "boolean" + }, + "mobileRedirectUri": { + "type": "string" + } + }, + "required": [ + "enabled", + "issuerUrl", + "clientId", + "clientSecret", + "scope", + "buttonText", + "autoRegister", + "autoLaunch", + "mobileOverrideEnabled", + "mobileRedirectUri" + ] + }, + "SystemConfigPasswordLoginDto": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ] + }, + "SystemConfigStorageTemplateDto": { + "type": "object", + "properties": { + "template": { + "type": "string" + } + }, + "required": [ + "template" + ] + }, + "SystemConfigDto": { + "type": "object", + "properties": { + "ffmpeg": { + "$ref": "#/components/schemas/SystemConfigFFmpegDto" + }, + "oauth": { + "$ref": "#/components/schemas/SystemConfigOAuthDto" + }, + "passwordLogin": { + "$ref": "#/components/schemas/SystemConfigPasswordLoginDto" + }, + "storageTemplate": { + "$ref": "#/components/schemas/SystemConfigStorageTemplateDto" + } + }, + "required": [ + "ffmpeg", + "oauth", + "passwordLogin", + "storageTemplate" + ] + }, + "SystemConfigTemplateStorageOptionDto": { + "type": "object", + "properties": { + "yearOptions": { + "type": "array", + "items": { + "type": "string" + } + }, + "monthOptions": { + "type": "array", + "items": { + "type": "string" + } + }, + "dayOptions": { + "type": "array", + "items": { + "type": "string" + } + }, + "hourOptions": { + "type": "array", + "items": { + "type": "string" + } + }, + "minuteOptions": { + "type": "array", + "items": { + "type": "string" + } + }, + "secondOptions": { + "type": "array", + "items": { + "type": "string" + } + }, + "presetOptions": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "yearOptions", + "monthOptions", + "dayOptions", + "hourOptions", + "minuteOptions", + "secondOptions", + "presetOptions" + ] + }, "UserResponseDto": { "type": "object", "properties": { @@ -4476,181 +4651,6 @@ "required": [ "command" ] - }, - "SystemConfigFFmpegDto": { - "type": "object", - "properties": { - "crf": { - "type": "string" - }, - "preset": { - "type": "string" - }, - "targetVideoCodec": { - "type": "string" - }, - "targetAudioCodec": { - "type": "string" - }, - "targetScaling": { - "type": "string" - } - }, - "required": [ - "crf", - "preset", - "targetVideoCodec", - "targetAudioCodec", - "targetScaling" - ] - }, - "SystemConfigOAuthDto": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "issuerUrl": { - "type": "string" - }, - "clientId": { - "type": "string" - }, - "clientSecret": { - "type": "string" - }, - "scope": { - "type": "string" - }, - "buttonText": { - "type": "string" - }, - "autoRegister": { - "type": "boolean" - }, - "autoLaunch": { - "type": "boolean" - }, - "mobileOverrideEnabled": { - "type": "boolean" - }, - "mobileRedirectUri": { - "type": "string" - } - }, - "required": [ - "enabled", - "issuerUrl", - "clientId", - "clientSecret", - "scope", - "buttonText", - "autoRegister", - "autoLaunch", - "mobileOverrideEnabled", - "mobileRedirectUri" - ] - }, - "SystemConfigPasswordLoginDto": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - } - }, - "required": [ - "enabled" - ] - }, - "SystemConfigStorageTemplateDto": { - "type": "object", - "properties": { - "template": { - "type": "string" - } - }, - "required": [ - "template" - ] - }, - "SystemConfigDto": { - "type": "object", - "properties": { - "ffmpeg": { - "$ref": "#/components/schemas/SystemConfigFFmpegDto" - }, - "oauth": { - "$ref": "#/components/schemas/SystemConfigOAuthDto" - }, - "passwordLogin": { - "$ref": "#/components/schemas/SystemConfigPasswordLoginDto" - }, - "storageTemplate": { - "$ref": "#/components/schemas/SystemConfigStorageTemplateDto" - } - }, - "required": [ - "ffmpeg", - "oauth", - "passwordLogin", - "storageTemplate" - ] - }, - "SystemConfigTemplateStorageOptionDto": { - "type": "object", - "properties": { - "yearOptions": { - "type": "array", - "items": { - "type": "string" - } - }, - "monthOptions": { - "type": "array", - "items": { - "type": "string" - } - }, - "dayOptions": { - "type": "array", - "items": { - "type": "string" - } - }, - "hourOptions": { - "type": "array", - "items": { - "type": "string" - } - }, - "minuteOptions": { - "type": "array", - "items": { - "type": "string" - } - }, - "secondOptions": { - "type": "array", - "items": { - "type": "string" - } - }, - "presetOptions": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "yearOptions", - "monthOptions", - "dayOptions", - "hourOptions", - "minuteOptions", - "secondOptions", - "presetOptions" - ] } } } diff --git a/server/libs/common/src/config/bull-queue.config.ts b/server/libs/common/src/config/bull-queue.config.ts deleted file mode 100644 index 0917ecbe9f..0000000000 --- a/server/libs/common/src/config/bull-queue.config.ts +++ /dev/null @@ -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, - }, - }), -}; diff --git a/server/libs/common/src/config/index.ts b/server/libs/common/src/config/index.ts index 4ae6b9100b..41e7ed15eb 100644 --- a/server/libs/common/src/config/index.ts +++ b/server/libs/common/src/config/index.ts @@ -1,2 +1 @@ export * from './app.config'; -export * from './bull-queue.config'; diff --git a/server/libs/domain/src/api-key/api-key.repository.ts b/server/libs/domain/src/api-key/api-key.repository.ts index a1dee0887b..961d521648 100644 --- a/server/libs/domain/src/api-key/api-key.repository.ts +++ b/server/libs/domain/src/api-key/api-key.repository.ts @@ -1,4 +1,4 @@ -import { APIKeyEntity } from '@app/infra'; +import { APIKeyEntity } from '@app/infra/db/entities'; export const IKeyRepository = 'IKeyRepository'; diff --git a/server/libs/domain/src/api-key/api-key.service.spec.ts b/server/libs/domain/src/api-key/api-key.service.spec.ts index a99bce5a24..0b9516af74 100644 --- a/server/libs/domain/src/api-key/api-key.service.spec.ts +++ b/server/libs/domain/src/api-key/api-key.service.spec.ts @@ -1,4 +1,4 @@ -import { APIKeyEntity } from '@app/infra'; +import { APIKeyEntity } from '@app/infra/db/entities'; import { BadRequestException, UnauthorizedException } from '@nestjs/common'; import { authStub, entityStub, newCryptoRepositoryMock, newKeyRepositoryMock } from '../../test'; import { ICryptoRepository } from '../auth'; diff --git a/server/libs/domain/src/api-key/api-key.service.ts b/server/libs/domain/src/api-key/api-key.service.ts index b9ef6ad997..aefff8f64d 100644 --- a/server/libs/domain/src/api-key/api-key.service.ts +++ b/server/libs/domain/src/api-key/api-key.service.ts @@ -1,4 +1,4 @@ -import { UserEntity } from '@app/infra'; +import { UserEntity } from '@app/infra/db/entities'; import { BadRequestException, Inject, Injectable, UnauthorizedException } from '@nestjs/common'; import { AuthUserDto, ICryptoRepository } from '../auth'; import { IKeyRepository } from './api-key.repository'; diff --git a/server/libs/domain/src/api-key/response-dto/api-key-response.dto.ts b/server/libs/domain/src/api-key/response-dto/api-key-response.dto.ts index 706a618b10..400712dfd3 100644 --- a/server/libs/domain/src/api-key/response-dto/api-key-response.dto.ts +++ b/server/libs/domain/src/api-key/response-dto/api-key-response.dto.ts @@ -1,4 +1,4 @@ -import { APIKeyEntity } from '@app/infra'; +import { APIKeyEntity } from '@app/infra/db/entities'; import { ApiProperty } from '@nestjs/swagger'; export class APIKeyResponseDto { diff --git a/server/libs/domain/src/domain.module.ts b/server/libs/domain/src/domain.module.ts index 56e958fb42..0a9b278b4c 100644 --- a/server/libs/domain/src/domain.module.ts +++ b/server/libs/domain/src/domain.module.ts @@ -1,11 +1,23 @@ import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common'; import { APIKeyService } from './api-key'; +import { SystemConfigService } from './system-config'; import { UserService } from './user'; +export const INITIAL_SYSTEM_CONFIG = 'INITIAL_SYSTEM_CONFIG'; + const providers: Provider[] = [ // APIKeyService, + SystemConfigService, UserService, + + { + provide: INITIAL_SYSTEM_CONFIG, + inject: [SystemConfigService], + useFactory: async (configService: SystemConfigService) => { + return configService.getConfig(); + }, + }, ]; @Global() diff --git a/server/libs/domain/src/index.ts b/server/libs/domain/src/index.ts index 2d3b6a23b7..f8160e5c65 100644 --- a/server/libs/domain/src/index.ts +++ b/server/libs/domain/src/index.ts @@ -1,4 +1,6 @@ export * from './api-key'; export * from './auth'; export * from './domain.module'; +export * from './job'; +export * from './system-config'; export * from './user'; diff --git a/server/libs/domain/src/job/index.ts b/server/libs/domain/src/job/index.ts new file mode 100644 index 0000000000..f790bf20ef --- /dev/null +++ b/server/libs/domain/src/job/index.ts @@ -0,0 +1,3 @@ +export * from './interfaces'; +export * from './job.constants'; +export * from './job.repository'; diff --git a/server/libs/job/src/interfaces/asset-uploaded.interface.ts b/server/libs/domain/src/job/interfaces/asset-uploaded.interface.ts similarity index 77% rename from server/libs/job/src/interfaces/asset-uploaded.interface.ts rename to server/libs/domain/src/job/interfaces/asset-uploaded.interface.ts index d21a552a8a..e4bf11685d 100644 --- a/server/libs/job/src/interfaces/asset-uploaded.interface.ts +++ b/server/libs/domain/src/job/interfaces/asset-uploaded.interface.ts @@ -1,4 +1,4 @@ -import { AssetEntity } from '@app/infra'; +import { AssetEntity } from '@app/infra/db/entities'; export interface IAssetUploadedJob { /** diff --git a/server/libs/domain/src/job/interfaces/background-task.interface.ts b/server/libs/domain/src/job/interfaces/background-task.interface.ts new file mode 100644 index 0000000000..bd3a611535 --- /dev/null +++ b/server/libs/domain/src/job/interfaces/background-task.interface.ts @@ -0,0 +1,5 @@ +import { AssetEntity } from '@app/infra/db/entities'; + +export interface IDeleteFileOnDiskJob { + assets: AssetEntity[]; +} diff --git a/server/libs/domain/src/job/interfaces/index.ts b/server/libs/domain/src/job/interfaces/index.ts new file mode 100644 index 0000000000..e08f93822a --- /dev/null +++ b/server/libs/domain/src/job/interfaces/index.ts @@ -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'; diff --git a/server/libs/job/src/interfaces/machine-learning.interface.ts b/server/libs/domain/src/job/interfaces/machine-learning.interface.ts similarity index 70% rename from server/libs/job/src/interfaces/machine-learning.interface.ts rename to server/libs/domain/src/job/interfaces/machine-learning.interface.ts index 4a6d9078e3..2f68d9d478 100644 --- a/server/libs/job/src/interfaces/machine-learning.interface.ts +++ b/server/libs/domain/src/job/interfaces/machine-learning.interface.ts @@ -1,4 +1,4 @@ -import { AssetEntity } from '@app/infra'; +import { AssetEntity } from '@app/infra/db/entities'; export interface IMachineLearningJob { /** diff --git a/server/libs/job/src/interfaces/metadata-extraction.interface.ts b/server/libs/domain/src/job/interfaces/metadata-extraction.interface.ts similarity index 92% rename from server/libs/job/src/interfaces/metadata-extraction.interface.ts rename to server/libs/domain/src/job/interfaces/metadata-extraction.interface.ts index e90d60c067..8140c8f393 100644 --- a/server/libs/job/src/interfaces/metadata-extraction.interface.ts +++ b/server/libs/domain/src/job/interfaces/metadata-extraction.interface.ts @@ -1,4 +1,4 @@ -import { AssetEntity } from '@app/infra'; +import { AssetEntity } from '@app/infra/db/entities'; export interface IExifExtractionProcessor { /** diff --git a/server/libs/job/src/interfaces/thumbnail-generation.interface.ts b/server/libs/domain/src/job/interfaces/thumbnail-generation.interface.ts similarity index 86% rename from server/libs/job/src/interfaces/thumbnail-generation.interface.ts rename to server/libs/domain/src/job/interfaces/thumbnail-generation.interface.ts index 8ea6976aa2..0bac83d34b 100644 --- a/server/libs/job/src/interfaces/thumbnail-generation.interface.ts +++ b/server/libs/domain/src/job/interfaces/thumbnail-generation.interface.ts @@ -1,4 +1,4 @@ -import { AssetEntity } from '@app/infra'; +import { AssetEntity } from '@app/infra/db/entities'; export interface JpegGeneratorProcessor { /** diff --git a/server/libs/job/src/interfaces/user-deletion.interface.ts b/server/libs/domain/src/job/interfaces/user-deletion.interface.ts similarity index 69% rename from server/libs/job/src/interfaces/user-deletion.interface.ts rename to server/libs/domain/src/job/interfaces/user-deletion.interface.ts index 432334a76a..4b4720b0c9 100644 --- a/server/libs/job/src/interfaces/user-deletion.interface.ts +++ b/server/libs/domain/src/job/interfaces/user-deletion.interface.ts @@ -1,4 +1,4 @@ -import { UserEntity } from '@app/infra'; +import { UserEntity } from '@app/infra/db/entities'; export interface IUserDeletionJob { /** diff --git a/server/libs/job/src/interfaces/video-transcode.interface.ts b/server/libs/domain/src/job/interfaces/video-transcode.interface.ts similarity index 78% rename from server/libs/job/src/interfaces/video-transcode.interface.ts rename to server/libs/domain/src/job/interfaces/video-transcode.interface.ts index 3dc20db277..6cc8870cfa 100644 --- a/server/libs/job/src/interfaces/video-transcode.interface.ts +++ b/server/libs/domain/src/job/interfaces/video-transcode.interface.ts @@ -1,4 +1,4 @@ -import { AssetEntity } from '@app/infra'; +import { AssetEntity } from '@app/infra/db/entities'; export interface IMp4ConversionProcessor { /** diff --git a/server/libs/job/src/constants/job-name.constant.ts b/server/libs/domain/src/job/job.constants.ts similarity index 57% rename from server/libs/job/src/constants/job-name.constant.ts rename to server/libs/domain/src/job/job.constants.ts index 4da798dc1d..e66c81e3c8 100644 --- a/server/libs/job/src/constants/job-name.constant.ts +++ b/server/libs/domain/src/job/job.constants.ts @@ -1,3 +1,15 @@ +export enum QueueName { + THUMBNAIL_GENERATION = 'thumbnail-generation-queue', + METADATA_EXTRACTION = 'metadata-extraction-queue', + VIDEO_CONVERSION = 'video-conversion-queue', + CHECKSUM_GENERATION = 'generate-checksum-queue', + ASSET_UPLOADED = 'asset-uploaded-queue', + MACHINE_LEARNING = 'machine-learning-queue', + USER_DELETION = 'user-deletion-queue', + CONFIG = 'config-queue', + BACKGROUND_TASK = 'background-task', +} + export enum JobName { ASSET_UPLOADED = 'asset-uploaded', MP4_CONVERSION = 'mp4-conversion', diff --git a/server/libs/domain/src/job/job.repository.ts b/server/libs/domain/src/job/job.repository.ts new file mode 100644 index 0000000000..66177a49a5 --- /dev/null +++ b/server/libs/domain/src/job/job.repository.ts @@ -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; +} diff --git a/server/libs/domain/src/system-config/dto/index.ts b/server/libs/domain/src/system-config/dto/index.ts new file mode 100644 index 0000000000..fa494a7a8d --- /dev/null +++ b/server/libs/domain/src/system-config/dto/index.ts @@ -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'; diff --git a/server/apps/immich/src/api-v1/system-config/dto/system-config-ffmpeg.dto.ts b/server/libs/domain/src/system-config/dto/system-config-ffmpeg.dto.ts similarity index 100% rename from server/apps/immich/src/api-v1/system-config/dto/system-config-ffmpeg.dto.ts rename to server/libs/domain/src/system-config/dto/system-config-ffmpeg.dto.ts diff --git a/server/apps/immich/src/api-v1/system-config/dto/system-config-oauth.dto.ts b/server/libs/domain/src/system-config/dto/system-config-oauth.dto.ts similarity index 100% rename from server/apps/immich/src/api-v1/system-config/dto/system-config-oauth.dto.ts rename to server/libs/domain/src/system-config/dto/system-config-oauth.dto.ts diff --git a/server/apps/immich/src/api-v1/system-config/dto/system-config-password-login.dto.ts b/server/libs/domain/src/system-config/dto/system-config-password-login.dto.ts similarity index 100% rename from server/apps/immich/src/api-v1/system-config/dto/system-config-password-login.dto.ts rename to server/libs/domain/src/system-config/dto/system-config-password-login.dto.ts diff --git a/server/apps/immich/src/api-v1/system-config/dto/system-config-storage-template.dto.ts b/server/libs/domain/src/system-config/dto/system-config-storage-template.dto.ts similarity index 100% rename from server/apps/immich/src/api-v1/system-config/dto/system-config-storage-template.dto.ts rename to server/libs/domain/src/system-config/dto/system-config-storage-template.dto.ts diff --git a/server/apps/immich/src/api-v1/system-config/dto/system-config.dto.ts b/server/libs/domain/src/system-config/dto/system-config.dto.ts similarity index 92% rename from server/apps/immich/src/api-v1/system-config/dto/system-config.dto.ts rename to server/libs/domain/src/system-config/dto/system-config.dto.ts index 9fc0035270..19a6fb4ab9 100644 --- a/server/apps/immich/src/api-v1/system-config/dto/system-config.dto.ts +++ b/server/libs/domain/src/system-config/dto/system-config.dto.ts @@ -1,4 +1,4 @@ -import { SystemConfig } from '@app/infra'; +import { SystemConfig } from '@app/infra/db/entities'; import { ValidateNested } from 'class-validator'; import { SystemConfigFFmpegDto } from './system-config-ffmpeg.dto'; import { SystemConfigOAuthDto } from './system-config-oauth.dto'; diff --git a/server/libs/domain/src/system-config/index.ts b/server/libs/domain/src/system-config/index.ts new file mode 100644 index 0000000000..d9a12bf372 --- /dev/null +++ b/server/libs/domain/src/system-config/index.ts @@ -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'; diff --git a/server/libs/domain/src/system-config/response-dto/index.ts b/server/libs/domain/src/system-config/response-dto/index.ts new file mode 100644 index 0000000000..9cb60bece7 --- /dev/null +++ b/server/libs/domain/src/system-config/response-dto/index.ts @@ -0,0 +1 @@ +export * from './system-config-template-storage-option.dto'; diff --git a/server/apps/immich/src/api-v1/system-config/response-dto/system-config-template-storage-option.dto.ts b/server/libs/domain/src/system-config/response-dto/system-config-template-storage-option.dto.ts similarity index 100% rename from server/apps/immich/src/api-v1/system-config/response-dto/system-config-template-storage-option.dto.ts rename to server/libs/domain/src/system-config/response-dto/system-config-template-storage-option.dto.ts diff --git a/server/libs/immich-config/src/immich-config.service.ts b/server/libs/domain/src/system-config/system-config.core.ts similarity index 82% rename from server/libs/immich-config/src/immich-config.service.ts rename to server/libs/domain/src/system-config/system-config.core.ts index 73a04eb7c2..6d1dd53b02 100644 --- a/server/libs/immich-config/src/immich-config.service.ts +++ b/server/libs/domain/src/system-config/system-config.core.ts @@ -1,9 +1,9 @@ -import { SystemConfig, SystemConfigEntity, SystemConfigKey } from '@app/infra'; +import { SystemConfig, SystemConfigEntity, SystemConfigKey } from '@app/infra/db/entities'; import { BadRequestException, Injectable, Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import * as _ from 'lodash'; import { Subject } from 'rxjs'; -import { DeepPartial, In, Repository } from 'typeorm'; +import { DeepPartial } from 'typeorm'; +import { ISystemConfigRepository } from './system-config.repository'; export type SystemConfigValidator = (config: SystemConfig) => void | Promise; @@ -37,16 +37,13 @@ const defaults: SystemConfig = Object.freeze({ }); @Injectable() -export class ImmichConfigService { - private logger = new Logger(ImmichConfigService.name); +export class SystemConfigCore { + private logger = new Logger(SystemConfigCore.name); private validators: SystemConfigValidator[] = []; public config$ = new Subject(); - constructor( - @InjectRepository(SystemConfigEntity) - private systemConfigRepository: Repository, - ) {} + constructor(private repository: ISystemConfigRepository) {} public getDefaults(): SystemConfig { return defaults; @@ -57,7 +54,7 @@ export class ImmichConfigService { } public async getConfig() { - const overrides = await this.systemConfigRepository.find(); + const overrides = await this.repository.load(); const config: DeepPartial = {}; for (const { key, value } of overrides) { // set via dot notation @@ -95,11 +92,11 @@ export class ImmichConfigService { } if (updates.length > 0) { - await this.systemConfigRepository.save(updates); + await this.repository.saveAll(updates); } if (deletes.length > 0) { - await this.systemConfigRepository.delete({ key: In(deletes.map((item) => item.key)) }); + await this.repository.deleteKeys(deletes.map((item) => item.key)); } const newConfig = await this.getConfig(); diff --git a/server/libs/storage/src/constants/supported-datetime-template.ts b/server/libs/domain/src/system-config/system-config.datetime-variables.ts similarity index 100% rename from server/libs/storage/src/constants/supported-datetime-template.ts rename to server/libs/domain/src/system-config/system-config.datetime-variables.ts diff --git a/server/libs/domain/src/system-config/system-config.repository.ts b/server/libs/domain/src/system-config/system-config.repository.ts new file mode 100644 index 0000000000..c95e73ae24 --- /dev/null +++ b/server/libs/domain/src/system-config/system-config.repository.ts @@ -0,0 +1,9 @@ +import { SystemConfigEntity } from '@app/infra/db/entities'; + +export const ISystemConfigRepository = 'ISystemConfigRepository'; + +export interface ISystemConfigRepository { + load(): Promise; + saveAll(items: SystemConfigEntity[]): Promise; + deleteKeys(keys: string[]): Promise; +} diff --git a/server/libs/domain/src/system-config/system-config.service.spec.ts b/server/libs/domain/src/system-config/system-config.service.spec.ts new file mode 100644 index 0000000000..715ea183d7 --- /dev/null +++ b/server/libs/domain/src/system-config/system-config.service.spec.ts @@ -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; + let jobMock: jest.Mocked; + + 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(); + }); + }); +}); diff --git a/server/libs/domain/src/system-config/system-config.service.ts b/server/libs/domain/src/system-config/system-config.service.ts new file mode 100644 index 0000000000..2bf6bd5eda --- /dev/null +++ b/server/libs/domain/src/system-config/system-config.service.ts @@ -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 { + const config = await this.core.getConfig(); + return mapConfig(config); + } + + getDefaults(): SystemConfigDto { + const config = this.core.getDefaults(); + return mapConfig(config); + } + + async updateConfig(dto: SystemConfigDto): Promise { + 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; + } +} diff --git a/server/libs/domain/src/user/response-dto/user-response.dto.ts b/server/libs/domain/src/user/response-dto/user-response.dto.ts index d3670e49f5..3e17f38014 100644 --- a/server/libs/domain/src/user/response-dto/user-response.dto.ts +++ b/server/libs/domain/src/user/response-dto/user-response.dto.ts @@ -1,4 +1,4 @@ -import { UserEntity } from '@app/infra'; +import { UserEntity } from '@app/infra/db/entities'; export class UserResponseDto { id!: string; diff --git a/server/libs/domain/src/user/user.core.ts b/server/libs/domain/src/user/user.core.ts index 126747f2a1..a1cc54f42b 100644 --- a/server/libs/domain/src/user/user.core.ts +++ b/server/libs/domain/src/user/user.core.ts @@ -1,4 +1,4 @@ -import { UserEntity } from '@app/infra'; +import { UserEntity } from '@app/infra/db/entities'; import { BadRequestException, ForbiddenException, diff --git a/server/libs/domain/src/user/user.repository.ts b/server/libs/domain/src/user/user.repository.ts index 664a8f20eb..fab52eeeba 100644 --- a/server/libs/domain/src/user/user.repository.ts +++ b/server/libs/domain/src/user/user.repository.ts @@ -1,4 +1,4 @@ -import { UserEntity } from '@app/infra'; +import { UserEntity } from '@app/infra/db/entities'; export interface UserListFilter { excludeId?: string; diff --git a/server/libs/domain/src/user/user.service.spec.ts b/server/libs/domain/src/user/user.service.spec.ts index 115b39e234..deb61f16fd 100644 --- a/server/libs/domain/src/user/user.service.spec.ts +++ b/server/libs/domain/src/user/user.service.spec.ts @@ -1,5 +1,5 @@ -import { IUserRepository } from '@app/domain'; -import { UserEntity } from '@app/infra'; +import { IUserRepository } from './user.repository'; +import { UserEntity } from '@app/infra/db/entities'; import { BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common'; import { when } from 'jest-when'; import { newUserRepositoryMock } from '../../test'; diff --git a/server/libs/domain/test/fixtures.ts b/server/libs/domain/test/fixtures.ts index 278fae59e5..f7d9222a1c 100644 --- a/server/libs/domain/test/fixtures.ts +++ b/server/libs/domain/test/fixtures.ts @@ -1,4 +1,4 @@ -import { UserEntity } from '@app/infra'; +import { SystemConfig, UserEntity } from '@app/infra/db/entities'; import { AuthUserDto } from '../src'; export const authStub = { @@ -42,3 +42,33 @@ export const entityStub = { tags: [], }), }; + +export const systemConfigStub = { + defaults: Object.freeze({ + ffmpeg: { + crf: '23', + preset: 'ultrafast', + targetAudioCodec: 'mp3', + targetScaling: '1280:-2', + targetVideoCodec: 'libx264', + }, + oauth: { + autoLaunch: false, + autoRegister: true, + buttonText: 'Login with OAuth', + clientId: '', + clientSecret: '', + enabled: false, + issuerUrl: '', + mobileOverrideEnabled: false, + mobileRedirectUri: '', + scope: 'openid email profile', + }, + passwordLogin: { + enabled: true, + }, + storageTemplate: { + template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', + }, + } as SystemConfig), +}; diff --git a/server/libs/domain/test/index.ts b/server/libs/domain/test/index.ts index 58d289be33..db8e2ede60 100644 --- a/server/libs/domain/test/index.ts +++ b/server/libs/domain/test/index.ts @@ -1,4 +1,6 @@ export * from './api-key.repository.mock'; export * from './crypto.repository.mock'; export * from './fixtures'; +export * from './job.repository.mock'; +export * from './system-config.repository.mock'; export * from './user.repository.mock'; diff --git a/server/libs/domain/test/job.repository.mock.ts b/server/libs/domain/test/job.repository.mock.ts new file mode 100644 index 0000000000..d75beed223 --- /dev/null +++ b/server/libs/domain/test/job.repository.mock.ts @@ -0,0 +1,7 @@ +import { IJobRepository } from '../src'; + +export const newJobRepositoryMock = (): jest.Mocked => { + return { + add: jest.fn().mockImplementation(() => Promise.resolve()), + }; +}; diff --git a/server/libs/domain/test/system-config.repository.mock.ts b/server/libs/domain/test/system-config.repository.mock.ts new file mode 100644 index 0000000000..23d69f99b7 --- /dev/null +++ b/server/libs/domain/test/system-config.repository.mock.ts @@ -0,0 +1,9 @@ +import { ISystemConfigRepository } from '../src'; + +export const newSystemConfigRepositoryMock = (): jest.Mocked => { + return { + load: jest.fn().mockResolvedValue([]), + saveAll: jest.fn().mockResolvedValue([]), + deleteKeys: jest.fn(), + }; +}; diff --git a/server/libs/immich-config/src/immich-config.module.ts b/server/libs/immich-config/src/immich-config.module.ts deleted file mode 100644 index c34c6e110a..0000000000 --- a/server/libs/immich-config/src/immich-config.module.ts +++ /dev/null @@ -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 {} diff --git a/server/libs/immich-config/src/index.ts b/server/libs/immich-config/src/index.ts deleted file mode 100644 index e0a5aa3ae8..0000000000 --- a/server/libs/immich-config/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './immich-config.module'; -export * from './immich-config.service'; diff --git a/server/libs/immich-config/tsconfig.lib.json b/server/libs/immich-config/tsconfig.lib.json deleted file mode 100644 index 43ec129ea7..0000000000 --- a/server/libs/immich-config/tsconfig.lib.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "declaration": true, - "outDir": "../../dist/libs/immich-config" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] -} diff --git a/server/libs/infra/src/db/repository/system-config.repository.ts b/server/libs/infra/src/db/repository/system-config.repository.ts new file mode 100644 index 0000000000..a977417ba0 --- /dev/null +++ b/server/libs/infra/src/db/repository/system-config.repository.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, + ) {} + + load(): Promise[]> { + return this.repository.find(); + } + + saveAll(items: SystemConfigEntity[]): Promise { + return this.repository.save(items); + } + + async deleteKeys(keys: string[]): Promise { + await this.repository.delete({ key: In(keys) }); + } +} diff --git a/server/libs/infra/src/infra.module.ts b/server/libs/infra/src/infra.module.ts index e6e2764041..f3be0ad5f0 100644 --- a/server/libs/infra/src/infra.module.ts +++ b/server/libs/infra/src/infra.module.ts @@ -1,26 +1,65 @@ -import { ICryptoRepository, IKeyRepository, IUserRepository } from '@app/domain'; +import { + ICryptoRepository, + IJobRepository, + IKeyRepository, + ISystemConfigRepository, + IUserRepository, + QueueName, +} from '@app/domain'; import { databaseConfig, UserEntity } from '@app/infra'; +import { BullModule } from '@nestjs/bull'; import { Global, Module, Provider } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { cryptoRepository } from './auth/crypto.repository'; -import { APIKeyEntity, UserRepository } from './db'; +import { APIKeyEntity, SystemConfigEntity, UserRepository } from './db'; import { APIKeyRepository } from './db/repository'; +import { SystemConfigRepository } from './db/repository/system-config.repository'; +import { JobRepository } from './job'; const providers: Provider[] = [ // { provide: ICryptoRepository, useValue: cryptoRepository }, { provide: IKeyRepository, useClass: APIKeyRepository }, + { provide: IJobRepository, useClass: JobRepository }, + { provide: ISystemConfigRepository, useClass: SystemConfigRepository }, { provide: IUserRepository, useClass: UserRepository }, ]; @Global() @Module({ imports: [ - // TypeOrmModule.forRoot(databaseConfig), - TypeOrmModule.forFeature([APIKeyEntity, UserEntity]), + TypeOrmModule.forFeature([APIKeyEntity, UserEntity, SystemConfigEntity]), + BullModule.forRootAsync({ + useFactory: async () => ({ + prefix: 'immich_bull', + redis: { + host: process.env.REDIS_HOSTNAME || 'immich_redis', + port: parseInt(process.env.REDIS_PORT || '6379'), + db: parseInt(process.env.REDIS_DBINDEX || '0'), + password: process.env.REDIS_PASSWORD || undefined, + path: process.env.REDIS_SOCKET || undefined, + }, + defaultJobOptions: { + attempts: 3, + removeOnComplete: true, + removeOnFail: false, + }, + }), + }), + BullModule.registerQueue( + { name: QueueName.USER_DELETION }, + { name: QueueName.THUMBNAIL_GENERATION }, + { name: QueueName.ASSET_UPLOADED }, + { name: QueueName.METADATA_EXTRACTION }, + { name: QueueName.VIDEO_CONVERSION }, + { name: QueueName.CHECKSUM_GENERATION }, + { name: QueueName.MACHINE_LEARNING }, + { name: QueueName.CONFIG }, + { name: QueueName.BACKGROUND_TASK }, + ), ], providers: [...providers], - exports: [...providers], + exports: [...providers, BullModule], }) export class InfraModule {} diff --git a/server/libs/infra/src/job/index.ts b/server/libs/infra/src/job/index.ts new file mode 100644 index 0000000000..39d17bf115 --- /dev/null +++ b/server/libs/infra/src/job/index.ts @@ -0,0 +1 @@ +export * from './job.repository'; diff --git a/server/libs/infra/src/job/job.repository.ts b/server/libs/infra/src/job/job.repository.ts new file mode 100644 index 0000000000..cdac756c54 --- /dev/null +++ b/server/libs/infra/src/job/job.repository.ts @@ -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 { + 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); + } + } +} diff --git a/server/libs/job/src/constants/bull-queue-registration.constant.ts b/server/libs/job/src/constants/bull-queue-registration.constant.ts deleted file mode 100644 index 8a05f06398..0000000000 --- a/server/libs/job/src/constants/bull-queue-registration.constant.ts +++ /dev/null @@ -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 }, -]; diff --git a/server/libs/job/src/constants/queue-name.constant.ts b/server/libs/job/src/constants/queue-name.constant.ts deleted file mode 100644 index 8a93bda243..0000000000 --- a/server/libs/job/src/constants/queue-name.constant.ts +++ /dev/null @@ -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', -} diff --git a/server/libs/job/src/index.ts b/server/libs/job/src/index.ts deleted file mode 100644 index b3aa4e829a..0000000000 --- a/server/libs/job/src/index.ts +++ /dev/null @@ -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'; diff --git a/server/libs/job/tsconfig.lib.json b/server/libs/job/tsconfig.lib.json deleted file mode 100644 index 52dbebd526..0000000000 --- a/server/libs/job/tsconfig.lib.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "declaration": true, - "outDir": "../../dist/libs/job" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] -} diff --git a/server/libs/storage/src/interfaces/immich-storage.interface.ts b/server/libs/storage/src/interfaces/immich-storage.interface.ts deleted file mode 100644 index c5d848e1d6..0000000000 --- a/server/libs/storage/src/interfaces/immich-storage.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface IImmichStorage { - write(): Promise; - read(): Promise; -} - -export enum IStorageType {} diff --git a/server/libs/storage/src/storage.module.ts b/server/libs/storage/src/storage.module.ts index b729d0db83..b88795fd20 100644 --- a/server/libs/storage/src/storage.module.ts +++ b/server/libs/storage/src/storage.module.ts @@ -1,11 +1,10 @@ -import { AssetEntity, SystemConfigEntity } from '@app/infra'; -import { ImmichConfigModule } from '@app/immich-config'; +import { AssetEntity } from '@app/infra'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { StorageService } from './storage.service'; @Module({ - imports: [TypeOrmModule.forFeature([AssetEntity, SystemConfigEntity]), ImmichConfigModule], + imports: [TypeOrmModule.forFeature([AssetEntity])], providers: [StorageService], exports: [StorageService], }) diff --git a/server/libs/storage/src/storage.service.ts b/server/libs/storage/src/storage.service.ts index fff681fb90..3b601e0c41 100644 --- a/server/libs/storage/src/storage.service.ts +++ b/server/libs/storage/src/storage.service.ts @@ -1,6 +1,6 @@ import { APP_UPLOAD_LOCATION } from '@app/common'; import { AssetEntity, AssetType, SystemConfig } from '@app/infra'; -import { ImmichConfigService, INITIAL_SYSTEM_CONFIG } from '@app/immich-config'; +import { SystemConfigService, INITIAL_SYSTEM_CONFIG } from '@app/domain'; import { Inject, Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import fsPromise from 'fs/promises'; @@ -19,7 +19,7 @@ import { supportedMonthTokens, supportedSecondTokens, supportedYearTokens, -} from './constants/supported-datetime-template'; +} from '@app/domain'; const moveFile = promisify(mv); @@ -32,14 +32,14 @@ export class StorageService { constructor( @InjectRepository(AssetEntity) private assetRepository: Repository, - private immichConfigService: ImmichConfigService, + private systemConfigService: SystemConfigService, @Inject(INITIAL_SYSTEM_CONFIG) config: SystemConfig, ) { this.storageTemplate = this.compile(config.storageTemplate.template); - this.immichConfigService.addValidator((config) => this.validateConfig(config)); + this.systemConfigService.addValidator((config) => this.validateConfig(config)); - this.immichConfigService.config$.subscribe((config) => { + this.systemConfigService.config$.subscribe((config) => { this.logger.debug(`Received new config, recompiling storage template: ${config.storageTemplate.template}`); this.storageTemplate = this.compile(config.storageTemplate.template); }); diff --git a/server/package.json b/server/package.json index d37f865e2e..578f4adc77 100644 --- a/server/package.json +++ b/server/package.json @@ -136,7 +136,8 @@ "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ - "**/*.(t|j)s" + "**/*.(t|j)s", + "!**/migrations/*" ], "coverageDirectory": "./coverage", "coverageThreshold": { @@ -145,10 +146,10 @@ "statements": 20 }, "./libs/domain/": { - "branches": 70, + "branches": 75, "functions": 85, - "lines": 85, - "statements": 85 + "lines": 90, + "statements": 90 } }, "testEnvironment": "node", @@ -158,9 +159,6 @@ ], "moduleNameMapper": { "@app/common": "/libs/common/src", - "^@app/job(|/.*)$": "/libs/job/src/$1", - "@app/job": "/libs/job/src", - "^@app/immich-config(|/.*)$": "/libs/immich-config/src/$1", "^@app/storage(|/.*)$": "/libs/storage/src/$1", "^@app/infra(|/.*)$": "/libs/infra/src/$1", "^@app/domain(|/.*)$": "/libs/domain/src/$1" diff --git a/server/tsconfig.json b/server/tsconfig.json index 39ecfcb2a8..506ec3b594 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -18,10 +18,6 @@ "paths": { "@app/common": ["libs/common/src"], "@app/common/*": ["libs/common/src/*"], - "@app/job": ["libs/job/src"], - "@app/job/*": ["libs/job/src/*"], - "@app/immich-config": ["libs/immich-config/src"], - "@app/immich-config/*": ["libs/immich-config/src/*"], "@app/storage": ["libs/storage/src"], "@app/storage/*": ["libs/storage/src/*"], "@app/infra": ["libs/infra/src"],