Kaynağa Gözat

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
Jason Rasmussen 2 yıl önce
ebeveyn
işleme
c0a6b3d5a3
92 değiştirilmiş dosya ile 860 ekleme ve 632 silme
  1. 1 1
      docker/.env.test
  2. 0 3
      server/apps/immich/src/api-v1/asset/asset.module.ts
  3. 1 1
      server/apps/immich/src/api-v1/asset/asset.service.spec.ts
  4. 1 1
      server/apps/immich/src/api-v1/asset/asset.service.ts
  5. 1 2
      server/apps/immich/src/api-v1/auth/auth.module.ts
  6. 3 3
      server/apps/immich/src/api-v1/auth/auth.service.spec.ts
  7. 2 2
      server/apps/immich/src/api-v1/auth/auth.service.ts
  8. 1 11
      server/apps/immich/src/api-v1/job/job.module.ts
  9. 8 2
      server/apps/immich/src/api-v1/job/job.service.ts
  10. 1 2
      server/apps/immich/src/api-v1/oauth/oauth.module.ts
  11. 3 3
      server/apps/immich/src/api-v1/oauth/oauth.service.spec.ts
  12. 3 4
      server/apps/immich/src/api-v1/oauth/oauth.service.ts
  13. 0 21
      server/apps/immich/src/api-v1/system-config/system-config.module.ts
  14. 0 54
      server/apps/immich/src/api-v1/system-config/system-config.service.ts
  15. 2 2
      server/apps/immich/src/app.controller.ts
  16. 3 10
      server/apps/immich/src/app.module.ts
  17. 1 0
      server/apps/immich/src/controllers/index.ts
  18. 2 4
      server/apps/immich/src/controllers/system-config.controller.ts
  19. 1 1
      server/apps/immich/src/modules/background-task/background-task.module.ts
  20. 1 1
      server/apps/immich/src/modules/background-task/background-task.processor.ts
  21. 1 1
      server/apps/immich/src/modules/background-task/background-task.service.ts
  22. 1 6
      server/apps/immich/src/modules/schedule-tasks/schedule-tasks.module.ts
  23. 2 2
      server/apps/immich/src/modules/schedule-tasks/schedule-tasks.service.ts
  24. 0 2
      server/apps/immich/test/jest-e2e.json
  25. 1 8
      server/apps/microservices/src/microservices.module.ts
  26. 1 1
      server/apps/microservices/src/microservices.service.ts
  27. 1 1
      server/apps/microservices/src/processors/asset-uploaded.processor.ts
  28. 1 1
      server/apps/microservices/src/processors/generate-checksum.processor.ts
  29. 2 2
      server/apps/microservices/src/processors/machine-learning.processor.ts
  30. 1 1
      server/apps/microservices/src/processors/metadata-extraction.processor.ts
  31. 4 4
      server/apps/microservices/src/processors/storage-migration.processor.ts
  32. 2 2
      server/apps/microservices/src/processors/thumbnail.processor.ts
  33. 2 2
      server/apps/microservices/src/processors/user-deletion.processor.ts
  34. 5 5
      server/apps/microservices/src/processors/video-transcode.processor.ts
  35. 309 309
      server/immich-openapi-specs.json
  36. 0 19
      server/libs/common/src/config/bull-queue.config.ts
  37. 0 1
      server/libs/common/src/config/index.ts
  38. 1 1
      server/libs/domain/src/api-key/api-key.repository.ts
  39. 1 1
      server/libs/domain/src/api-key/api-key.service.spec.ts
  40. 1 1
      server/libs/domain/src/api-key/api-key.service.ts
  41. 1 1
      server/libs/domain/src/api-key/response-dto/api-key-response.dto.ts
  42. 12 0
      server/libs/domain/src/domain.module.ts
  43. 2 0
      server/libs/domain/src/index.ts
  44. 3 0
      server/libs/domain/src/job/index.ts
  45. 1 1
      server/libs/domain/src/job/interfaces/asset-uploaded.interface.ts
  46. 5 0
      server/libs/domain/src/job/interfaces/background-task.interface.ts
  47. 7 0
      server/libs/domain/src/job/interfaces/index.ts
  48. 1 1
      server/libs/domain/src/job/interfaces/machine-learning.interface.ts
  49. 1 1
      server/libs/domain/src/job/interfaces/metadata-extraction.interface.ts
  50. 1 1
      server/libs/domain/src/job/interfaces/thumbnail-generation.interface.ts
  51. 1 1
      server/libs/domain/src/job/interfaces/user-deletion.interface.ts
  52. 1 1
      server/libs/domain/src/job/interfaces/video-transcode.interface.ts
  53. 12 0
      server/libs/domain/src/job/job.constants.ts
  54. 32 0
      server/libs/domain/src/job/job.repository.ts
  55. 5 0
      server/libs/domain/src/system-config/dto/index.ts
  56. 0 0
      server/libs/domain/src/system-config/dto/system-config-ffmpeg.dto.ts
  57. 0 0
      server/libs/domain/src/system-config/dto/system-config-oauth.dto.ts
  58. 0 0
      server/libs/domain/src/system-config/dto/system-config-password-login.dto.ts
  59. 0 0
      server/libs/domain/src/system-config/dto/system-config-storage-template.dto.ts
  60. 1 1
      server/libs/domain/src/system-config/dto/system-config.dto.ts
  61. 5 0
      server/libs/domain/src/system-config/index.ts
  62. 1 0
      server/libs/domain/src/system-config/response-dto/index.ts
  63. 0 0
      server/libs/domain/src/system-config/response-dto/system-config-template-storage-option.dto.ts
  64. 9 12
      server/libs/domain/src/system-config/system-config.core.ts
  65. 0 0
      server/libs/domain/src/system-config/system-config.datetime-variables.ts
  66. 9 0
      server/libs/domain/src/system-config/system-config.repository.ts
  67. 156 0
      server/libs/domain/src/system-config/system-config.service.spec.ts
  68. 68 0
      server/libs/domain/src/system-config/system-config.service.ts
  69. 1 1
      server/libs/domain/src/user/response-dto/user-response.dto.ts
  70. 1 1
      server/libs/domain/src/user/user.core.ts
  71. 1 1
      server/libs/domain/src/user/user.repository.ts
  72. 2 2
      server/libs/domain/src/user/user.service.spec.ts
  73. 31 1
      server/libs/domain/test/fixtures.ts
  74. 2 0
      server/libs/domain/test/index.ts
  75. 7 0
      server/libs/domain/test/job.repository.mock.ts
  76. 9 0
      server/libs/domain/test/system-config.repository.mock.ts
  77. 0 24
      server/libs/immich-config/src/immich-config.module.ts
  78. 0 2
      server/libs/immich-config/src/index.ts
  79. 0 9
      server/libs/immich-config/tsconfig.lib.json
  80. 23 0
      server/libs/infra/src/db/repository/system-config.repository.ts
  81. 44 5
      server/libs/infra/src/infra.module.ts
  82. 1 0
      server/libs/infra/src/job/index.ts
  83. 21 0
      server/libs/infra/src/job/job.repository.ts
  84. 0 16
      server/libs/job/src/constants/bull-queue-registration.constant.ts
  85. 0 11
      server/libs/job/src/constants/queue-name.constant.ts
  86. 0 7
      server/libs/job/src/index.ts
  87. 0 9
      server/libs/job/tsconfig.lib.json
  88. 0 6
      server/libs/storage/src/interfaces/immich-storage.interface.ts
  89. 2 3
      server/libs/storage/src/storage.module.ts
  90. 5 5
      server/libs/storage/src/storage.service.ts
  91. 5 7
      server/package.json
  92. 0 4
      server/tsconfig.json

+ 1 - 1
docker/.env.test

@@ -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

+ 0 - 3
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 { 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],

+ 1 - 1
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 { 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';

+ 1 - 1
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 { 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 - 2
server/apps/immich/src/api-v1/auth/auth.module.ts

@@ -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],
 })
 })

+ 3 - 3
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 { 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,

+ 2 - 2
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 { 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);

+ 1 - 11
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 { 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: [
-    TypeOrmModule.forFeature([ExifEntity]),
-    ImmichJwtModule,
-    TagModule,
-    AssetModule,
-    StorageModule,
-    BullModule.registerQueue(...immichSharedQueues),
-  ],
+  imports: [TypeOrmModule.forFeature([ExifEntity]), ImmichJwtModule, TagModule, AssetModule, StorageModule],
   controllers: [JobController],
   controllers: [JobController],
   providers: [JobService],
   providers: [JobService],
 })
 })

+ 8 - 2
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 { 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 - 2
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 { 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],

+ 3 - 3
server/apps/immich/src/api-v1/oauth/oauth.service.spec.ts

@@ -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);
   });
   });

+ 3 - 4
server/apps/immich/src/api-v1/oauth/oauth.service.ts

@@ -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> {

+ 0 - 21
server/apps/immich/src/api-v1/system-config/system-config.module.ts

@@ -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 {}

+ 0 - 54
server/apps/immich/src/api-v1/system-config/system-config.service.ts

@@ -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;
-  }
-}

+ 2 - 2
server/apps/immich/src/app.controller.ts

@@ -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')

+ 3 - 10
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 { 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 - 0
server/apps/immich/src/controllers/index.ts

@@ -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';

+ 2 - 4
server/apps/immich/src/api-v1/system-config/system-config.controller.ts → 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 { 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 { 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')
 @ApiTags('System Config')
 @ApiBearerAuth()
 @ApiBearerAuth()

+ 1 - 1
server/apps/immich/src/modules/background-task/background-task.module.ts

@@ -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 - 1
server/apps/immich/src/modules/background-task/background-task.processor.ts

@@ -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 - 1
server/apps/immich/src/modules/background-task/background-task.service.ts

@@ -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 - 6
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 { 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: [
-    TypeOrmModule.forFeature([AssetEntity, ExifEntity, UserEntity]),
-    BullModule.registerQueue(...immichSharedQueues),
-  ],
+  imports: [TypeOrmModule.forFeature([AssetEntity, ExifEntity, UserEntity])],
   providers: [ScheduleTasksService],
   providers: [ScheduleTasksService],
 })
 })
 export class ScheduleTasksModule {}
 export class ScheduleTasksModule {}

+ 2 - 2
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 { 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()

+ 0 - 2
server/apps/immich/test/jest-e2e.json

@@ -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 - 8
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 { 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 - 1
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 { InjectQueue } from '@nestjs/bull';
 import { Injectable, OnModuleInit } from '@nestjs/common';
 import { Injectable, OnModuleInit } from '@nestjs/common';
 import { Queue } from 'bull';
 import { Queue } from 'bull';

+ 1 - 1
server/apps/microservices/src/processors/asset-uploaded.processor.ts

@@ -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 - 1
server/apps/microservices/src/processors/generate-checksum.processor.ts

@@ -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';

+ 2 - 2
server/apps/microservices/src/processors/machine-learning.processor.ts

@@ -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 { 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 { 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 - 1
server/apps/microservices/src/processors/metadata-extraction.processor.ts

@@ -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';

+ 4 - 4
server/apps/microservices/src/processors/storage-migration.processor.ts

@@ -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 { QueueName, JobName } from '@app/job';
+import { SystemConfigService } from '@app/domain';
+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();
   }
   }
 }
 }

+ 2 - 2
server/apps/microservices/src/processors/thumbnail.processor.ts

@@ -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 {

+ 2 - 2
server/apps/microservices/src/processors/user-deletion.processor.ts

@@ -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 { 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 { 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 - 5
server/apps/microservices/src/processors/video-transcode.processor.ts

@@ -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 { 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 { 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)

+ 309 - 309
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": {
     "/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,35 +2882,210 @@
           "name"
           "name"
         ]
         ]
       },
       },
-      "UserResponseDto": {
+      "SystemConfigFFmpegDto": {
         "type": "object",
         "type": "object",
         "properties": {
         "properties": {
-          "id": {
-            "type": "string"
-          },
-          "email": {
+          "crf": {
             "type": "string"
             "type": "string"
           },
           },
-          "firstName": {
+          "preset": {
             "type": "string"
             "type": "string"
           },
           },
-          "lastName": {
+          "targetVideoCodec": {
             "type": "string"
             "type": "string"
           },
           },
-          "createdAt": {
+          "targetAudioCodec": {
             "type": "string"
             "type": "string"
           },
           },
-          "profileImagePath": {
+          "targetScaling": {
             "type": "string"
             "type": "string"
-          },
-          "shouldChangePassword": {
-            "type": "boolean"
-          },
-          "isAdmin": {
-            "type": "boolean"
-          },
-          "deletedAt": {
-            "format": "date-time",
+          }
+        },
+        "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": {
+          "id": {
+            "type": "string"
+          },
+          "email": {
+            "type": "string"
+          },
+          "firstName": {
+            "type": "string"
+          },
+          "lastName": {
+            "type": "string"
+          },
+          "createdAt": {
+            "type": "string"
+          },
+          "profileImagePath": {
+            "type": "string"
+          },
+          "shouldChangePassword": {
+            "type": "boolean"
+          },
+          "isAdmin": {
+            "type": "boolean"
+          },
+          "deletedAt": {
+            "format": "date-time",
             "type": "string"
             "type": "string"
           },
           },
           "oauthId": {
           "oauthId": {
@@ -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"
-        ]
       }
       }
     }
     }
   }
   }

+ 0 - 19
server/libs/common/src/config/bull-queue.config.ts

@@ -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,
-    },
-  }),
-};

+ 0 - 1
server/libs/common/src/config/index.ts

@@ -1,2 +1 @@
 export * from './app.config';
 export * from './app.config';
-export * from './bull-queue.config';

+ 1 - 1
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';
 export const IKeyRepository = 'IKeyRepository';
 
 

+ 1 - 1
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 { 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 - 1
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 { 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 - 1
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';
 import { ApiProperty } from '@nestjs/swagger';
 
 
 export class APIKeyResponseDto {
 export class APIKeyResponseDto {

+ 12 - 0
server/libs/domain/src/domain.module.ts

@@ -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()

+ 2 - 0
server/libs/domain/src/index.ts

@@ -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 - 0
server/libs/domain/src/job/index.ts

@@ -0,0 +1,3 @@
+export * from './interfaces';
+export * from './job.constants';
+export * from './job.repository';

+ 1 - 1
server/libs/job/src/interfaces/asset-uploaded.interface.ts → 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 {
 export interface IAssetUploadedJob {
   /**
   /**

+ 5 - 0
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[];
+}

+ 7 - 0
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';

+ 1 - 1
server/libs/job/src/interfaces/machine-learning.interface.ts → 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 {
 export interface IMachineLearningJob {
   /**
   /**

+ 1 - 1
server/libs/job/src/interfaces/metadata-extraction.interface.ts → 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 {
 export interface IExifExtractionProcessor {
   /**
   /**

+ 1 - 1
server/libs/job/src/interfaces/thumbnail-generation.interface.ts → 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 {
 export interface JpegGeneratorProcessor {
   /**
   /**

+ 1 - 1
server/libs/job/src/interfaces/user-deletion.interface.ts → 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 {
 export interface IUserDeletionJob {
   /**
   /**

+ 1 - 1
server/libs/job/src/interfaces/video-transcode.interface.ts → 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 {
 export interface IMp4ConversionProcessor {
   /**
   /**

+ 12 - 0
server/libs/job/src/constants/job-name.constant.ts → 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 {
 export enum JobName {
   ASSET_UPLOADED = 'asset-uploaded',
   ASSET_UPLOADED = 'asset-uploaded',
   MP4_CONVERSION = 'mp4-conversion',
   MP4_CONVERSION = 'mp4-conversion',

+ 32 - 0
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<void>;
+}

+ 5 - 0
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';

+ 0 - 0
server/apps/immich/src/api-v1/system-config/dto/system-config-ffmpeg.dto.ts → server/libs/domain/src/system-config/dto/system-config-ffmpeg.dto.ts


+ 0 - 0
server/apps/immich/src/api-v1/system-config/dto/system-config-oauth.dto.ts → server/libs/domain/src/system-config/dto/system-config-oauth.dto.ts


+ 0 - 0
server/apps/immich/src/api-v1/system-config/dto/system-config-password-login.dto.ts → server/libs/domain/src/system-config/dto/system-config-password-login.dto.ts


+ 0 - 0
server/apps/immich/src/api-v1/system-config/dto/system-config-storage-template.dto.ts → server/libs/domain/src/system-config/dto/system-config-storage-template.dto.ts


+ 1 - 1
server/apps/immich/src/api-v1/system-config/dto/system-config.dto.ts → 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 { 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 - 0
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';

+ 1 - 0
server/libs/domain/src/system-config/response-dto/index.ts

@@ -0,0 +1 @@
+export * from './system-config-template-storage-option.dto';

+ 0 - 0
server/apps/immich/src/api-v1/system-config/response-dto/system-config-template-storage-option.dto.ts → server/libs/domain/src/system-config/response-dto/system-config-template-storage-option.dto.ts


+ 9 - 12
server/libs/immich-config/src/immich-config.service.ts → 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 { 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 {
-  private logger = new Logger(ImmichConfigService.name);
+export class SystemConfigCore {
+  private logger = new Logger(SystemConfigCore.name);
   private validators: SystemConfigValidator[] = [];
   private validators: SystemConfigValidator[] = [];
 
 
   public config$ = new Subject<SystemConfig>();
   public config$ = new Subject<SystemConfig>();
 
 
-  constructor(
-    @InjectRepository(SystemConfigEntity)
-    private systemConfigRepository: Repository<SystemConfigEntity>,
-  ) {}
+  constructor(private repository: ISystemConfigRepository) {}
 
 
   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
server/libs/storage/src/constants/supported-datetime-template.ts → server/libs/domain/src/system-config/system-config.datetime-variables.ts


+ 9 - 0
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<SystemConfigEntity[]>;
+  saveAll(items: SystemConfigEntity[]): Promise<SystemConfigEntity[]>;
+  deleteKeys(keys: string[]): Promise<void>;
+}

+ 156 - 0
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<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();
+    });
+  });
+});

+ 68 - 0
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<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 - 1
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 {
 export class UserResponseDto {
   id!: string;
   id!: string;

+ 1 - 1
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 {
 import {
   BadRequestException,
   BadRequestException,
   ForbiddenException,
   ForbiddenException,

+ 1 - 1
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 {
 export interface UserListFilter {
   excludeId?: string;
   excludeId?: string;

+ 2 - 2
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 { BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common';
 import { when } from 'jest-when';
 import { when } from 'jest-when';
 import { newUserRepositoryMock } from '../../test';
 import { newUserRepositoryMock } from '../../test';

+ 31 - 1
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';
 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),
+};

+ 2 - 0
server/libs/domain/test/index.ts

@@ -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 - 0
server/libs/domain/test/job.repository.mock.ts

@@ -0,0 +1,7 @@
+import { IJobRepository } from '../src';
+
+export const newJobRepositoryMock = (): jest.Mocked<IJobRepository> => {
+  return {
+    add: jest.fn().mockImplementation(() => Promise.resolve()),
+  };
+};

+ 9 - 0
server/libs/domain/test/system-config.repository.mock.ts

@@ -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(),
+  };
+};

+ 0 - 24
server/libs/immich-config/src/immich-config.module.ts

@@ -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 {}

+ 0 - 2
server/libs/immich-config/src/index.ts

@@ -1,2 +0,0 @@
-export * from './immich-config.module';
-export * from './immich-config.service';

+ 0 - 9
server/libs/immich-config/tsconfig.lib.json

@@ -1,9 +0,0 @@
-{
-  "extends": "../../tsconfig.json",
-  "compilerOptions": {
-    "declaration": true,
-    "outDir": "../../dist/libs/immich-config"
-  },
-  "include": ["src/**/*"],
-  "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
-}

+ 23 - 0
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<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) });
+  }
+}

+ 44 - 5
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 { 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 - 0
server/libs/infra/src/job/index.ts

@@ -0,0 +1 @@
+export * from './job.repository';

+ 21 - 0
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<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);
+    }
+  }
+}

+ 0 - 16
server/libs/job/src/constants/bull-queue-registration.constant.ts

@@ -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 },
-];

+ 0 - 11
server/libs/job/src/constants/queue-name.constant.ts

@@ -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',
-}

+ 0 - 7
server/libs/job/src/index.ts

@@ -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';

+ 0 - 9
server/libs/job/tsconfig.lib.json

@@ -1,9 +0,0 @@
-{
-  "extends": "../../tsconfig.json",
-  "compilerOptions": {
-    "declaration": true,
-    "outDir": "../../dist/libs/job"
-  },
-  "include": ["src/**/*"],
-  "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
-}

+ 0 - 6
server/libs/storage/src/interfaces/immich-storage.interface.ts

@@ -1,6 +0,0 @@
-export interface IImmichStorage {
-  write(): Promise<void>;
-  read(): Promise<void>;
-}
-
-export enum IStorageType {}

+ 2 - 3
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 { 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],
 })
 })

+ 5 - 5
server/libs/storage/src/storage.service.ts

@@ -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);
     });
     });

+ 5 - 7
server/package.json

@@ -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,
-        "statements": 85
+        "lines": 90,
+        "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"

+ 0 - 4
server/tsconfig.json

@@ -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"],