From 16d0df796c3dec465096db83d409aef85c1185bd Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 20 Mar 2024 22:15:09 -0500 Subject: [PATCH] refactor: infra folder (#8138) --- .github/workflows/test.yml | 2 +- docs/docs/developer/database-migrations.md | 2 +- server/package.json | 23 +-- server/src/apps/api.main.ts | 8 +- server/src/apps/api.service.ts | 6 +- server/src/apps/app.module.ts | 6 +- server/src/apps/microservices.main.ts | 8 +- server/src/apps/microservices.service.ts | 5 +- server/src/config.ts | 2 +- server/src/constants.ts | 105 ++++++++++ server/src/controllers/auth.controller.ts | 2 +- .../src/controllers/shared-link.controller.ts | 2 +- server/src/cores/access.core.ts | 2 +- server/src/cores/storage.core.ts | 4 +- server/src/cores/system-config.core.ts | 4 +- server/src/{infra => }/database.config.ts | 6 +- server/src/decorators.ts | 2 +- server/src/domain/auth/auth.constant.ts | 12 -- server/src/domain/job/job.constants.ts | 155 --------------- server/src/domain/job/job.interface.ts | 41 ---- server/src/domain/media/media.constant.ts | 1 - .../domain/smart-info/smart-info.constant.ts | 129 ------------- .../system-config/system-config.constants.ts | 27 --- server/src/dtos/job.dto.ts | 2 +- server/src/dtos/server-info.dto.ts | 2 +- server/src/dtos/system-config-job.dto.ts | 2 +- server/src/entities/system-config.entity.ts | 2 +- .../immich/api-v1/asset/asset-repository.ts | 2 +- .../immich/api-v1/asset/asset.service.spec.ts | 3 +- .../src/immich/api-v1/asset/asset.service.ts | 9 +- server/src/immich/app.utils.ts | 8 +- server/src/infra/sql-generator/sql.logger.ts | 25 --- server/src/interfaces/asset.repository.ts | 2 +- server/src/interfaces/database.repository.ts | 2 +- server/src/interfaces/job.repository.ts | 147 ++++++++++++-- server/src/interfaces/person.repository.ts | 2 +- server/src/interfaces/search.repository.ts | 2 +- server/src/middleware/auth.guard.ts | 4 +- server/src/middleware/error.interceptor.ts | 4 +- .../src/middleware/file-upload.interceptor.ts | 2 +- .../websocket.adapter.ts | 0 .../migrations/1700713871511-UsePgVectors.ts | 4 +- .../1700713994428-AddCLIPEmbeddingIndex.ts | 2 +- .../1700714033632-AddFaceEmbeddingIndex.ts | 2 +- .../sql => queries}/access.repository.sql | 0 .../sql => queries}/album.repository.sql | 0 .../sql => queries}/api.key.repository.sql | 0 .../sql => queries}/asset.repository.sql | 0 .../sql => queries}/audit.repository.sql | 0 .../sql => queries}/library.repository.sql | 0 .../sql => queries}/move.repository.sql | 0 .../sql => queries}/partner.repository.sql | 0 .../sql => queries}/person.repository.sql | 0 .../sql => queries}/search.repository.sql | 0 .../shared.link.repository.sql | 0 .../system.config.repository.sql | 0 .../system.metadata.repository.sql | 0 .../{infra/sql => queries}/tag.repository.sql | 0 .../sql => queries}/user.repository.sql | 0 .../sql => queries}/user.token.repository.sql | 0 server/src/repositories/access.repository.ts | 2 +- .../src/repositories/activity.repository.ts | 2 +- server/src/repositories/album.repository.ts | 6 +- server/src/repositories/api-key.repository.ts | 2 +- .../repositories/asset-stack.repository.ts | 2 +- server/src/repositories/asset.repository.ts | 6 +- server/src/repositories/audit.repository.ts | 2 +- .../repositories/communication.repository.ts | 4 +- server/src/repositories/crypto.repository.ts | 2 +- .../src/repositories/database.repository.ts | 8 +- .../src/repositories/filesystem.provider.ts | 6 +- server/src/repositories/job.repository.ts | 76 +++++++- server/src/repositories/library.repository.ts | 2 +- .../machine-learning.repository.ts | 2 +- server/src/repositories/media.repository.ts | 6 +- .../src/repositories/metadata.repository.ts | 12 +- server/src/repositories/move.repository.ts | 2 +- server/src/repositories/partner.repository.ts | 2 +- server/src/repositories/person.repository.ts | 6 +- server/src/repositories/search.repository.ts | 12 +- .../repositories/server-info.repository.ts | 2 +- .../repositories/shared-link.repository.ts | 2 +- .../repositories/system-config.repository.ts | 2 +- .../system-metadata.repository.ts | 2 +- server/src/repositories/tag.repository.ts | 2 +- .../src/repositories/user-token.repository.ts | 2 +- server/src/repositories/user.repository.ts | 2 +- server/src/services/album.service.ts | 2 +- server/src/services/asset.service.spec.ts | 3 +- server/src/services/asset.service.ts | 18 +- server/src/services/audit.service.ts | 9 +- server/src/services/auth.service.spec.ts | 2 +- server/src/services/auth.service.ts | 12 +- server/src/services/database.service.spec.ts | 4 +- server/src/services/database.service.ts | 4 +- server/src/services/download.service.spec.ts | 2 +- server/src/services/download.service.ts | 6 +- server/src/services/job.service.spec.ts | 11 +- server/src/services/job.service.ts | 15 +- server/src/services/library.service.spec.ts | 4 +- server/src/services/library.service.ts | 20 +- server/src/services/media.service.spec.ts | 3 +- server/src/services/media.service.ts | 37 ++-- server/src/services/metadata.service.spec.ts | 3 +- server/src/services/metadata.service.ts | 18 +- server/src/services/person.service.spec.ts | 5 +- server/src/services/person.service.ts | 23 ++- .../src/services/server-info.service.spec.ts | 2 +- server/src/services/server-info.service.ts | 8 +- server/src/services/shared-link.service.ts | 2 +- .../src/services/smart-info.service.spec.ts | 17 +- server/src/services/smart-info.service.ts | 16 +- .../src/services/storage-template.service.ts | 15 +- server/src/services/storage.service.ts | 5 +- .../services/system-config.service.spec.ts | 4 +- server/src/services/system-config.service.ts | 6 +- server/src/services/trash.service.spec.ts | 3 +- server/src/services/trash.service.ts | 5 +- server/src/services/user.service.spec.ts | 5 +- server/src/services/user.service.ts | 8 +- server/src/utils.spec.ts | 103 ---------- server/src/utils.ts | 181 ------------------ server/src/utils/bytes.ts | 24 +++ .../infra.utils.ts => utils/database.ts} | 54 +----- server/src/utils/file.ts | 25 +++ .../src/{infra => utils}/instrumentation.ts | 2 +- server/src/{infra => utils}/logger.ts | 0 .../media/media.util.ts => utils/media.ts} | 0 .../mime-types.spec.ts} | 73 +------ .../mime-types.ts} | 93 +-------- server/src/utils/misc.ts | 32 ++++ server/src/utils/pagination.ts | 79 ++++++++ server/src/utils/set.ts | 36 ++++ .../sql-generator/index.ts => utils/sql.ts} | 30 ++- server/src/utils/version.spec.ts | 72 +++++++ server/src/utils/version.ts | 64 +++++++ server/test/fixtures/library.stub.ts | 2 +- .../repositories/database.repository.mock.ts | 2 +- server/test/utils.ts | 5 +- 139 files changed, 968 insertions(+), 1164 deletions(-) create mode 100644 server/src/constants.ts rename server/src/{infra => }/database.config.ts (86%) delete mode 100644 server/src/domain/auth/auth.constant.ts delete mode 100644 server/src/domain/job/job.constants.ts delete mode 100644 server/src/domain/job/job.interface.ts delete mode 100644 server/src/domain/media/media.constant.ts delete mode 100644 server/src/domain/smart-info/smart-info.constant.ts delete mode 100644 server/src/domain/system-config/system-config.constants.ts delete mode 100644 server/src/infra/sql-generator/sql.logger.ts rename server/src/{infra => middleware}/websocket.adapter.ts (100%) rename server/src/{infra/sql => queries}/access.repository.sql (100%) rename server/src/{infra/sql => queries}/album.repository.sql (100%) rename server/src/{infra/sql => queries}/api.key.repository.sql (100%) rename server/src/{infra/sql => queries}/asset.repository.sql (100%) rename server/src/{infra/sql => queries}/audit.repository.sql (100%) rename server/src/{infra/sql => queries}/library.repository.sql (100%) rename server/src/{infra/sql => queries}/move.repository.sql (100%) rename server/src/{infra/sql => queries}/partner.repository.sql (100%) rename server/src/{infra/sql => queries}/person.repository.sql (100%) rename server/src/{infra/sql => queries}/search.repository.sql (100%) rename server/src/{infra/sql => queries}/shared.link.repository.sql (100%) rename server/src/{infra/sql => queries}/system.config.repository.sql (100%) rename server/src/{infra/sql => queries}/system.metadata.repository.sql (100%) rename server/src/{infra/sql => queries}/tag.repository.sql (100%) rename server/src/{infra/sql => queries}/user.repository.sql (100%) rename server/src/{infra/sql => queries}/user.token.repository.sql (100%) delete mode 100644 server/src/utils.spec.ts delete mode 100644 server/src/utils.ts create mode 100644 server/src/utils/bytes.ts rename server/src/{infra/infra.utils.ts => utils/database.ts} (75%) create mode 100644 server/src/utils/file.ts rename server/src/{infra => utils}/instrumentation.ts (98%) rename server/src/{infra => utils}/logger.ts (100%) rename server/src/{domain/media/media.util.ts => utils/media.ts} (100%) rename server/src/{domain/domain.constant.spec.ts => utils/mime-types.spec.ts} (68%) rename server/src/{domain/domain.constant.ts => utils/mime-types.ts} (56%) create mode 100644 server/src/utils/misc.ts create mode 100644 server/src/utils/pagination.ts create mode 100644 server/src/utils/set.ts rename server/src/{infra/sql-generator/index.ts => utils/sql.ts} (90%) create mode 100644 server/src/utils/version.spec.ts create mode 100644 server/src/utils/version.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 13f5a73a15..6a5df111d7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -354,7 +354,7 @@ jobs: id: verify-changed-sql-files with: files: | - server/src/infra/sql + server/src/queries - name: Verify SQL files have not changed if: steps.verify-changed-sql-files.outputs.files_changed == 'true' diff --git a/docs/docs/developer/database-migrations.md b/docs/docs/developer/database-migrations.md index eccf78b44d..2cddf5f386 100644 --- a/docs/docs/developer/database-migrations.md +++ b/docs/docs/developer/database-migrations.md @@ -5,7 +5,7 @@ After making any changes in the `server/src/entities`, a database migration need 1. Run the command ```bash -npm run typeorm:migrations:generate ./src/infra/ +npm run typeorm:migrations:generate ``` 2. Check if the migration file makes sense. diff --git a/server/package.json b/server/package.json index eb65eadf09..4776e418f4 100644 --- a/server/package.json +++ b/server/package.json @@ -25,12 +25,12 @@ "e2e:jobs": "jest --config e2e/jobs/jest-e2e.json --runInBand", "typeorm": "typeorm", "typeorm:migrations:create": "typeorm migration:create", - "typeorm:migrations:generate": "typeorm migration:generate -d ./dist/infra/database.config.js", - "typeorm:migrations:run": "typeorm migration:run -d ./dist/infra/database.config.js", - "typeorm:migrations:revert": "typeorm migration:revert -d ./dist/infra/database.config.js", - "typeorm:schema:drop": "typeorm query -d ./dist/infra/database.config.js 'DROP schema public cascade; CREATE schema public;'", + "typeorm:migrations:generate": "typeorm migration:generate -d ./dist/database.config.js", + "typeorm:migrations:run": "typeorm migration:run -d ./dist/database.config.js", + "typeorm:migrations:revert": "typeorm migration:revert -d ./dist/database.config.js", + "typeorm:schema:drop": "typeorm query -d ./dist/database.config.js 'DROP schema public cascade; CREATE schema public;'", "typeorm:schema:reset": "npm run typeorm:schema:drop && npm run typeorm:migrations:run", - "sql:generate": "node ./dist/infra/sql-generator/" + "sql:generate": "node ./dist/utils/sql.js" }, "dependencies": { "@babel/runtime": "^7.22.11", @@ -146,15 +146,16 @@ "^.+\\.ts$": "ts-jest" }, "collectCoverageFrom": [ - "/src/**/*.(t|j)s", - "!/src/infra/**/*", - "!/src/migrations/**/*", - "!/src/subscribers/**/*", - "!/src/immich/controllers/**/*" + "/src/cores/*.(t|j)s", + "/src/dtos/*.(t|j)s", + "/src/interfaces/*.(t|j)s", + "/src/services/*.(t|j)s", + "/src/utils/*.(t|j)s", + "/src/*.t|j)s" ], "coverageDirectory": "./coverage", "coverageThreshold": { - "./src/domain/": { + "./src/": { "branches": 75, "functions": 80, "lines": 85, diff --git a/server/src/apps/api.main.ts b/server/src/apps/api.main.ts index 9ffdd1d482..07685308e0 100644 --- a/server/src/apps/api.main.ts +++ b/server/src/apps/api.main.ts @@ -7,11 +7,11 @@ import sirv from 'sirv'; import { ApiModule } from 'src/apps/api.module'; import { ApiService } from 'src/apps/api.service'; import { excludePaths } from 'src/config'; -import { WEB_ROOT, envName, isDev, serverVersion } from 'src/domain/domain.constant'; +import { WEB_ROOT, envName, isDev, serverVersion } from 'src/constants'; import { useSwagger } from 'src/immich/app.utils'; -import { otelSDK } from 'src/infra/instrumentation'; -import { ImmichLogger } from 'src/infra/logger'; -import { WebSocketAdapter } from 'src/infra/websocket.adapter'; +import { WebSocketAdapter } from 'src/middleware/websocket.adapter'; +import { otelSDK } from 'src/utils/instrumentation'; +import { ImmichLogger } from 'src/utils/logger'; const logger = new ImmichLogger('ImmichServer'); const port = Number(process.env.SERVER_PORT) || 3001; diff --git a/server/src/apps/api.service.ts b/server/src/apps/api.service.ts index fbd0d0e4a7..87107b23f4 100644 --- a/server/src/apps/api.service.ts +++ b/server/src/apps/api.service.ts @@ -3,8 +3,7 @@ import { Cron, CronExpression, Interval } from '@nestjs/schedule'; import { NextFunction, Request, Response } from 'express'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; -import { ONE_HOUR, WEB_ROOT } from 'src/domain/domain.constant'; -import { ImmichLogger } from 'src/infra/logger'; +import { ONE_HOUR, WEB_ROOT } from 'src/constants'; import { AuthService } from 'src/services/auth.service'; import { DatabaseService } from 'src/services/database.service'; import { JobService } from 'src/services/job.service'; @@ -12,7 +11,8 @@ import { ServerInfoService } from 'src/services/server-info.service'; import { SharedLinkService } from 'src/services/shared-link.service'; import { StorageService } from 'src/services/storage.service'; import { SystemConfigService } from 'src/services/system-config.service'; -import { OpenGraphTags } from 'src/utils'; +import { ImmichLogger } from 'src/utils/logger'; +import { OpenGraphTags } from 'src/utils/misc'; const render = (index: string, meta: OpenGraphTags) => { const tags = ` diff --git a/server/src/apps/app.module.ts b/server/src/apps/app.module.ts index 964e2d4e11..14aae51d9f 100644 --- a/server/src/apps/app.module.ts +++ b/server/src/apps/app.module.ts @@ -6,10 +6,8 @@ import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule'; import { TypeOrmModule } from '@nestjs/typeorm'; import { OpenTelemetryModule } from 'nestjs-otel'; import { bullConfig, bullQueues, immichAppConfig } from 'src/config'; +import { databaseConfig } from 'src/database.config'; import { databaseEntities } from 'src/entities'; -import { databaseConfig } from 'src/infra/database.config'; -import { otelConfig } from 'src/infra/instrumentation'; -import { ImmichLogger } from 'src/infra/logger'; import { IAccessRepository } from 'src/interfaces/access.repository'; import { IActivityRepository } from 'src/interfaces/activity.repository'; import { IAlbumRepository } from 'src/interfaces/album.repository'; @@ -88,6 +86,8 @@ import { SystemConfigService } from 'src/services/system-config.service'; import { TagService } from 'src/services/tag.service'; import { TrashService } from 'src/services/trash.service'; import { UserService } from 'src/services/user.service'; +import { otelConfig } from 'src/utils/instrumentation'; +import { ImmichLogger } from 'src/utils/logger'; const services: Provider[] = [ APIKeyService, diff --git a/server/src/apps/microservices.main.ts b/server/src/apps/microservices.main.ts index 552fb714c8..d35483d72e 100644 --- a/server/src/apps/microservices.main.ts +++ b/server/src/apps/microservices.main.ts @@ -1,9 +1,9 @@ import { NestFactory } from '@nestjs/core'; import { MicroservicesModule } from 'src/apps/microservices.module'; -import { envName, serverVersion } from 'src/domain/domain.constant'; -import { otelSDK } from 'src/infra/instrumentation'; -import { ImmichLogger } from 'src/infra/logger'; -import { WebSocketAdapter } from 'src/infra/websocket.adapter'; +import { envName, serverVersion } from 'src/constants'; +import { WebSocketAdapter } from 'src/middleware/websocket.adapter'; +import { otelSDK } from 'src/utils/instrumentation'; +import { ImmichLogger } from 'src/utils/logger'; const logger = new ImmichLogger('ImmichMicroservice'); const port = Number(process.env.MICROSERVICES_PORT) || 3002; diff --git a/server/src/apps/microservices.service.ts b/server/src/apps/microservices.service.ts index 8f11583beb..a6b7f3869f 100644 --- a/server/src/apps/microservices.service.ts +++ b/server/src/apps/microservices.service.ts @@ -1,7 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { JobName } from 'src/domain/job/job.constants'; -import { IDeleteFilesJob } from 'src/domain/job/job.interface'; -import { otelSDK } from 'src/infra/instrumentation'; +import { IDeleteFilesJob, JobName } from 'src/interfaces/job.repository'; import { AssetService } from 'src/services/asset.service'; import { AuditService } from 'src/services/audit.service'; import { DatabaseService } from 'src/services/database.service'; @@ -15,6 +13,7 @@ import { StorageTemplateService } from 'src/services/storage-template.service'; import { StorageService } from 'src/services/storage.service'; import { SystemConfigService } from 'src/services/system-config.service'; import { UserService } from 'src/services/user.service'; +import { otelSDK } from 'src/utils/instrumentation'; @Injectable() export class MicroservicesService { diff --git a/server/src/config.ts b/server/src/config.ts index b97f0bb7ea..f19546093a 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -3,8 +3,8 @@ import { ConfigModuleOptions } from '@nestjs/config'; import { QueueOptions } from 'bullmq'; import { RedisOptions } from 'ioredis'; import Joi from 'joi'; -import { QueueName } from 'src/domain/job/job.constants'; import { LogLevel } from 'src/entities/system-config.entity'; +import { QueueName } from 'src/interfaces/job.repository'; const WHEN_DB_URL_SET = Joi.when('DB_URL', { is: Joi.exist(), diff --git a/server/src/constants.ts b/server/src/constants.ts new file mode 100644 index 0000000000..eaacf12d92 --- /dev/null +++ b/server/src/constants.ts @@ -0,0 +1,105 @@ +import { Duration } from 'luxon'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { Version } from 'src/utils/version'; + +const { version } = JSON.parse(readFileSync('./package.json', 'utf8')); +export const serverVersion = Version.fromString(version); + +export const AUDIT_LOG_MAX_DURATION = Duration.fromObject({ days: 100 }); +export const ONE_HOUR = Duration.fromObject({ hours: 1 }); + +export const envName = (process.env.NODE_ENV || 'development').toUpperCase(); +export const isDev = process.env.NODE_ENV === 'development'; +export const APP_MEDIA_LOCATION = process.env.IMMICH_MEDIA_LOCATION || './upload'; +export const WEB_ROOT = process.env.IMMICH_WEB_ROOT || '/usr/src/app/www'; + +const GEODATA_ROOT_PATH = process.env.IMMICH_REVERSE_GEOCODING_ROOT || '/usr/src/resources'; + +export const citiesFile = 'cities500.txt'; +export const geodataDatePath = join(GEODATA_ROOT_PATH, 'geodata-date.txt'); +export const geodataAdmin1Path = join(GEODATA_ROOT_PATH, 'admin1CodesASCII.txt'); +export const geodataAdmin2Path = join(GEODATA_ROOT_PATH, 'admin2Codes.txt'); +export const geodataCities500Path = join(GEODATA_ROOT_PATH, citiesFile); + +export const MOBILE_REDIRECT = 'app.immich:/'; +export const LOGIN_URL = '/auth/login?autoLaunch=0'; +export const IMMICH_ACCESS_COOKIE = 'immich_access_token'; +export const IMMICH_IS_AUTHENTICATED = 'immich_is_authenticated'; +export const IMMICH_AUTH_TYPE_COOKIE = 'immich_auth_type'; +export const IMMICH_API_KEY_NAME = 'api_key'; +export const IMMICH_API_KEY_HEADER = 'x-api-key'; +export const IMMICH_SHARED_LINK_ACCESS_COOKIE = 'immich_shared_link_token'; +export enum AuthType { + PASSWORD = 'password', + OAUTH = 'oauth', +} + +export const FACE_THUMBNAIL_SIZE = 250; + +export const supportedYearTokens = ['y', 'yy']; +export const supportedMonthTokens = ['M', 'MM', 'MMM', 'MMMM']; +export const supportedWeekTokens = ['W', 'WW']; +export const supportedDayTokens = ['d', 'dd']; +export const supportedHourTokens = ['h', 'hh', 'H', 'HH']; +export const supportedMinuteTokens = ['m', 'mm']; +export const supportedSecondTokens = ['s', 'ss', 'SSS']; +export const supportedPresetTokens = [ + '{{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}}', + '{{y}}/{{y}}-{{MM}}/{{filename}}', + '{{y}}/{{y}}-{{WW}}/{{filename}}', + '{{y}}/{{y}}-{{MM}}-{{dd}}/{{assetId}}', + '{{y}}/{{y}}-{{MM}}/{{assetId}}', + '{{y}}/{{y}}-{{WW}}/{{assetId}}', + '{{album}}/{{filename}}', +]; + +type ModelInfo = { dimSize: number }; +export const CLIP_MODEL_INFO: Record = { + RN50__openai: { dimSize: 1024 }, + RN50__yfcc15m: { dimSize: 1024 }, + RN50__cc12m: { dimSize: 1024 }, + RN101__openai: { dimSize: 512 }, + RN101__yfcc15m: { dimSize: 512 }, + RN50x4__openai: { dimSize: 640 }, + RN50x16__openai: { dimSize: 768 }, + RN50x64__openai: { dimSize: 1024 }, + 'ViT-B-32__openai': { dimSize: 512 }, + 'ViT-B-32__laion2b_e16': { dimSize: 512 }, + 'ViT-B-32__laion400m_e31': { dimSize: 512 }, + 'ViT-B-32__laion400m_e32': { dimSize: 512 }, + 'ViT-B-32__laion2b-s34b-b79k': { dimSize: 512 }, + 'ViT-B-16__openai': { dimSize: 512 }, + 'ViT-B-16__laion400m_e31': { dimSize: 512 }, + 'ViT-B-16__laion400m_e32': { dimSize: 512 }, + 'ViT-B-16-plus-240__laion400m_e31': { dimSize: 640 }, + 'ViT-B-16-plus-240__laion400m_e32': { dimSize: 640 }, + 'ViT-L-14__openai': { dimSize: 768 }, + 'ViT-L-14__laion400m_e31': { dimSize: 768 }, + 'ViT-L-14__laion400m_e32': { dimSize: 768 }, + 'ViT-L-14__laion2b-s32b-b82k': { dimSize: 768 }, + 'ViT-L-14-336__openai': { dimSize: 768 }, + 'ViT-L-14-quickgelu__dfn2b': { dimSize: 768 }, + 'ViT-H-14__laion2b-s32b-b79k': { dimSize: 1024 }, + 'ViT-H-14-quickgelu__dfn5b': { dimSize: 1024 }, + 'ViT-H-14-378-quickgelu__dfn5b': { dimSize: 1024 }, + 'ViT-g-14__laion2b-s12b-b42k': { dimSize: 1024 }, + 'LABSE-Vit-L-14': { dimSize: 768 }, + 'XLM-Roberta-Large-Vit-B-32': { dimSize: 512 }, + 'XLM-Roberta-Large-Vit-B-16Plus': { dimSize: 640 }, + 'XLM-Roberta-Large-Vit-L-14': { dimSize: 768 }, + 'XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k': { dimSize: 1024 }, + 'nllb-clip-base-siglip__v1': { dimSize: 768 }, + 'nllb-clip-large-siglip__v1': { dimSize: 1152 }, +}; diff --git a/server/src/controllers/auth.controller.ts b/server/src/controllers/auth.controller.ts index cbb649dc08..9b4e7a3bc7 100644 --- a/server/src/controllers/auth.controller.ts +++ b/server/src/controllers/auth.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Req, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; -import { IMMICH_ACCESS_COOKIE, IMMICH_AUTH_TYPE_COOKIE, IMMICH_IS_AUTHENTICATED } from 'src/domain/auth/auth.constant'; +import { IMMICH_ACCESS_COOKIE, IMMICH_AUTH_TYPE_COOKIE, IMMICH_IS_AUTHENTICATED } from 'src/constants'; import { AuthDeviceResponseDto, AuthDto, diff --git a/server/src/controllers/shared-link.controller.ts b/server/src/controllers/shared-link.controller.ts index 61dd6605a0..990f4e3225 100644 --- a/server/src/controllers/shared-link.controller.ts +++ b/server/src/controllers/shared-link.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, Req, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; -import { IMMICH_SHARED_LINK_ACCESS_COOKIE } from 'src/domain/auth/auth.constant'; +import { IMMICH_SHARED_LINK_ACCESS_COOKIE } from 'src/constants'; import { AssetIdsResponseDto } from 'src/dtos/asset-ids.response.dto'; import { AssetIdsDto } from 'src/dtos/asset.dto'; import { AuthDto } from 'src/dtos/auth.dto'; diff --git a/server/src/cores/access.core.ts b/server/src/cores/access.core.ts index 1c0b1dabb6..78b30e4b18 100644 --- a/server/src/cores/access.core.ts +++ b/server/src/cores/access.core.ts @@ -2,7 +2,7 @@ import { BadRequestException, UnauthorizedException } from '@nestjs/common'; import { AuthDto } from 'src/dtos/auth.dto'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { IAccessRepository } from 'src/interfaces/access.repository'; -import { setDifference, setIsEqual, setUnion } from 'src/utils'; +import { setDifference, setIsEqual, setUnion } from 'src/utils/set'; export enum Permission { ACTIVITY_CREATE = 'activity.create', diff --git a/server/src/cores/storage.core.ts b/server/src/cores/storage.core.ts index 8bf65b2482..17f3f03600 100644 --- a/server/src/cores/storage.core.ts +++ b/server/src/cores/storage.core.ts @@ -1,16 +1,16 @@ import { dirname, join, resolve } from 'node:path'; +import { APP_MEDIA_LOCATION } from 'src/constants'; import { SystemConfigCore } from 'src/cores/system-config.core'; -import { APP_MEDIA_LOCATION } from 'src/domain/domain.constant'; import { AssetEntity } from 'src/entities/asset.entity'; import { AssetPathType, PathType, PersonPathType } from 'src/entities/move.entity'; import { PersonEntity } from 'src/entities/person.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { IAssetRepository } from 'src/interfaces/asset.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; import { IMoveRepository } from 'src/interfaces/move.repository'; import { IPersonRepository } from 'src/interfaces/person.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { ImmichLogger } from 'src/utils/logger'; export enum StorageFolder { ENCODED_VIDEO = 'encoded-video', diff --git a/server/src/cores/system-config.core.ts b/server/src/cores/system-config.core.ts index a6692fa185..6554839a54 100644 --- a/server/src/cores/system-config.core.ts +++ b/server/src/cores/system-config.core.ts @@ -5,7 +5,6 @@ import { validate } from 'class-validator'; import { load as loadYaml } from 'js-yaml'; import * as _ from 'lodash'; import { Subject } from 'rxjs'; -import { QueueName } from 'src/domain/job/job.constants'; import { SystemConfigDto } from 'src/dtos/system-config.dto'; import { AudioCodec, @@ -21,8 +20,9 @@ import { TranscodePolicy, VideoCodec, } from 'src/entities/system-config.entity'; -import { ImmichLogger } from 'src/infra/logger'; +import { QueueName } from 'src/interfaces/job.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { ImmichLogger } from 'src/utils/logger'; export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise; diff --git a/server/src/infra/database.config.ts b/server/src/database.config.ts similarity index 86% rename from server/src/infra/database.config.ts rename to server/src/database.config.ts index 572288c59c..e4e251ffc5 100644 --- a/server/src/infra/database.config.ts +++ b/server/src/database.config.ts @@ -16,9 +16,9 @@ const urlOrParts = url /* eslint unicorn/prefer-module: "off" -- We can fix this when migrating to ESM*/ export const databaseConfig: PostgresConnectionOptions = { type: 'postgres', - entities: [__dirname + '/../entities/*.entity.{js,ts}'], - migrations: [__dirname + '/../migrations/*.{js,ts}'], - subscribers: [__dirname + '/../subscribers/*.{js,ts}'], + entities: [__dirname + '/entities/*.entity.{js,ts}'], + migrations: [__dirname + '/migrations/*.{js,ts}'], + subscribers: [__dirname + '/subscribers/*.{js,ts}'], migrationsRun: false, synchronize: false, connectTimeoutMS: 10_000, // 10 seconds diff --git a/server/src/decorators.ts b/server/src/decorators.ts index 06dc0bfdcc..33efdafa39 100644 --- a/server/src/decorators.ts +++ b/server/src/decorators.ts @@ -1,6 +1,6 @@ import { SetMetadata } from '@nestjs/common'; import _ from 'lodash'; -import { setUnion } from 'src/utils'; +import { setUnion } from 'src/utils/set'; // PostgreSQL uses a 16-bit integer to indicate the number of bound parameters. This means that the // maximum number of parameters is 65535. Any query that tries to bind more than that (e.g. searching diff --git a/server/src/domain/auth/auth.constant.ts b/server/src/domain/auth/auth.constant.ts deleted file mode 100644 index f29fc92741..0000000000 --- a/server/src/domain/auth/auth.constant.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const MOBILE_REDIRECT = 'app.immich:/'; -export const LOGIN_URL = '/auth/login?autoLaunch=0'; -export const IMMICH_ACCESS_COOKIE = 'immich_access_token'; -export const IMMICH_IS_AUTHENTICATED = 'immich_is_authenticated'; -export const IMMICH_AUTH_TYPE_COOKIE = 'immich_auth_type'; -export const IMMICH_API_KEY_NAME = 'api_key'; -export const IMMICH_API_KEY_HEADER = 'x-api-key'; -export const IMMICH_SHARED_LINK_ACCESS_COOKIE = 'immich_shared_link_token'; -export enum AuthType { - PASSWORD = 'password', - OAUTH = 'oauth', -} diff --git a/server/src/domain/job/job.constants.ts b/server/src/domain/job/job.constants.ts deleted file mode 100644 index fe3c817278..0000000000 --- a/server/src/domain/job/job.constants.ts +++ /dev/null @@ -1,155 +0,0 @@ -export enum QueueName { - THUMBNAIL_GENERATION = 'thumbnailGeneration', - METADATA_EXTRACTION = 'metadataExtraction', - VIDEO_CONVERSION = 'videoConversion', - FACE_DETECTION = 'faceDetection', - FACIAL_RECOGNITION = 'facialRecognition', - SMART_SEARCH = 'smartSearch', - BACKGROUND_TASK = 'backgroundTask', - STORAGE_TEMPLATE_MIGRATION = 'storageTemplateMigration', - MIGRATION = 'migration', - SEARCH = 'search', - SIDECAR = 'sidecar', - LIBRARY = 'library', -} - -export type ConcurrentQueueName = Exclude< - QueueName, - QueueName.STORAGE_TEMPLATE_MIGRATION | QueueName.FACIAL_RECOGNITION ->; - -export enum JobCommand { - START = 'start', - PAUSE = 'pause', - RESUME = 'resume', - EMPTY = 'empty', - CLEAR_FAILED = 'clear-failed', -} - -export enum JobName { - // conversion - QUEUE_VIDEO_CONVERSION = 'queue-video-conversion', - VIDEO_CONVERSION = 'video-conversion', - - // thumbnails - QUEUE_GENERATE_THUMBNAILS = 'queue-generate-thumbnails', - GENERATE_JPEG_THUMBNAIL = 'generate-jpeg-thumbnail', - GENERATE_WEBP_THUMBNAIL = 'generate-webp-thumbnail', - GENERATE_THUMBHASH_THUMBNAIL = 'generate-thumbhash-thumbnail', - GENERATE_PERSON_THUMBNAIL = 'generate-person-thumbnail', - - // metadata - QUEUE_METADATA_EXTRACTION = 'queue-metadata-extraction', - METADATA_EXTRACTION = 'metadata-extraction', - LINK_LIVE_PHOTOS = 'link-live-photos', - - // user - USER_DELETION = 'user-deletion', - USER_DELETE_CHECK = 'user-delete-check', - USER_SYNC_USAGE = 'user-sync-usage', - - // asset - ASSET_DELETION = 'asset-deletion', - ASSET_DELETION_CHECK = 'asset-deletion-check', - - // storage template - STORAGE_TEMPLATE_MIGRATION = 'storage-template-migration', - STORAGE_TEMPLATE_MIGRATION_SINGLE = 'storage-template-migration-single', - - // migration - QUEUE_MIGRATION = 'queue-migration', - MIGRATE_ASSET = 'migrate-asset', - MIGRATE_PERSON = 'migrate-person', - - // facial recognition - PERSON_CLEANUP = 'person-cleanup', - QUEUE_FACE_DETECTION = 'queue-face-detection', - FACE_DETECTION = 'face-detection', - QUEUE_FACIAL_RECOGNITION = 'queue-facial-recognition', - FACIAL_RECOGNITION = 'facial-recognition', - - // library managment - LIBRARY_SCAN = 'library-refresh', - LIBRARY_SCAN_ASSET = 'library-refresh-asset', - LIBRARY_REMOVE_OFFLINE = 'library-remove-offline', - LIBRARY_DELETE = 'library-delete', - LIBRARY_QUEUE_SCAN_ALL = 'library-queue-all-refresh', - LIBRARY_QUEUE_CLEANUP = 'library-queue-cleanup', - - // cleanup - DELETE_FILES = 'delete-files', - CLEAN_OLD_AUDIT_LOGS = 'clean-old-audit-logs', - - // smart search - QUEUE_SMART_SEARCH = 'queue-smart-search', - SMART_SEARCH = 'smart-search', - - // XMP sidecars - QUEUE_SIDECAR = 'queue-sidecar', - SIDECAR_DISCOVERY = 'sidecar-discovery', - SIDECAR_SYNC = 'sidecar-sync', - SIDECAR_WRITE = 'sidecar-write', -} - -export const JOBS_ASSET_PAGINATION_SIZE = 1000; - -export const JOBS_TO_QUEUE: Record = { - // misc - [JobName.ASSET_DELETION]: QueueName.BACKGROUND_TASK, - [JobName.ASSET_DELETION_CHECK]: QueueName.BACKGROUND_TASK, - [JobName.USER_DELETE_CHECK]: QueueName.BACKGROUND_TASK, - [JobName.USER_DELETION]: QueueName.BACKGROUND_TASK, - [JobName.DELETE_FILES]: QueueName.BACKGROUND_TASK, - [JobName.CLEAN_OLD_AUDIT_LOGS]: QueueName.BACKGROUND_TASK, - [JobName.PERSON_CLEANUP]: QueueName.BACKGROUND_TASK, - [JobName.USER_SYNC_USAGE]: QueueName.BACKGROUND_TASK, - - // conversion - [JobName.QUEUE_VIDEO_CONVERSION]: QueueName.VIDEO_CONVERSION, - [JobName.VIDEO_CONVERSION]: QueueName.VIDEO_CONVERSION, - - // thumbnails - [JobName.QUEUE_GENERATE_THUMBNAILS]: QueueName.THUMBNAIL_GENERATION, - [JobName.GENERATE_JPEG_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, - [JobName.GENERATE_WEBP_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, - [JobName.GENERATE_THUMBHASH_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, - [JobName.GENERATE_PERSON_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, - - // metadata - [JobName.QUEUE_METADATA_EXTRACTION]: QueueName.METADATA_EXTRACTION, - [JobName.METADATA_EXTRACTION]: QueueName.METADATA_EXTRACTION, - [JobName.LINK_LIVE_PHOTOS]: QueueName.METADATA_EXTRACTION, - - // storage template - [JobName.STORAGE_TEMPLATE_MIGRATION]: QueueName.STORAGE_TEMPLATE_MIGRATION, - [JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE]: QueueName.STORAGE_TEMPLATE_MIGRATION, - - // migration - [JobName.QUEUE_MIGRATION]: QueueName.MIGRATION, - [JobName.MIGRATE_ASSET]: QueueName.MIGRATION, - [JobName.MIGRATE_PERSON]: QueueName.MIGRATION, - - // facial recognition - [JobName.QUEUE_FACE_DETECTION]: QueueName.FACE_DETECTION, - [JobName.FACE_DETECTION]: QueueName.FACE_DETECTION, - [JobName.QUEUE_FACIAL_RECOGNITION]: QueueName.FACIAL_RECOGNITION, - [JobName.FACIAL_RECOGNITION]: QueueName.FACIAL_RECOGNITION, - - // smart search - [JobName.QUEUE_SMART_SEARCH]: QueueName.SMART_SEARCH, - [JobName.SMART_SEARCH]: QueueName.SMART_SEARCH, - - // XMP sidecars - [JobName.QUEUE_SIDECAR]: QueueName.SIDECAR, - [JobName.SIDECAR_DISCOVERY]: QueueName.SIDECAR, - [JobName.SIDECAR_SYNC]: QueueName.SIDECAR, - [JobName.SIDECAR_WRITE]: QueueName.SIDECAR, - - // Library management - [JobName.LIBRARY_SCAN_ASSET]: QueueName.LIBRARY, - [JobName.LIBRARY_SCAN]: QueueName.LIBRARY, - [JobName.LIBRARY_DELETE]: QueueName.LIBRARY, - [JobName.LIBRARY_REMOVE_OFFLINE]: QueueName.LIBRARY, - [JobName.LIBRARY_QUEUE_SCAN_ALL]: QueueName.LIBRARY, - [JobName.LIBRARY_QUEUE_CLEANUP]: QueueName.LIBRARY, -}; diff --git a/server/src/domain/job/job.interface.ts b/server/src/domain/job/job.interface.ts deleted file mode 100644 index 8e7420bf97..0000000000 --- a/server/src/domain/job/job.interface.ts +++ /dev/null @@ -1,41 +0,0 @@ -export interface IBaseJob { - force?: boolean; -} - -export interface IEntityJob extends IBaseJob { - id: string; - source?: 'upload' | 'sidecar-write'; -} - -export interface IAssetDeletionJob extends IEntityJob { - fromExternal?: boolean; -} - -export interface ILibraryFileJob extends IEntityJob { - ownerId: string; - assetPath: string; -} - -export interface ILibraryRefreshJob extends IEntityJob { - refreshModifiedFiles: boolean; - refreshAllFiles: boolean; -} - -export interface IBulkEntityJob extends IBaseJob { - ids: string[]; -} - -export interface IDeleteFilesJob extends IBaseJob { - files: Array; -} - -export interface ISidecarWriteJob extends IEntityJob { - description?: string; - dateTimeOriginal?: string; - latitude?: number; - longitude?: number; -} - -export interface IDeferrableJob extends IEntityJob { - deferred?: boolean; -} diff --git a/server/src/domain/media/media.constant.ts b/server/src/domain/media/media.constant.ts deleted file mode 100644 index 3a8ee414b9..0000000000 --- a/server/src/domain/media/media.constant.ts +++ /dev/null @@ -1 +0,0 @@ -export const FACE_THUMBNAIL_SIZE = 250; diff --git a/server/src/domain/smart-info/smart-info.constant.ts b/server/src/domain/smart-info/smart-info.constant.ts deleted file mode 100644 index 66c31b9851..0000000000 --- a/server/src/domain/smart-info/smart-info.constant.ts +++ /dev/null @@ -1,129 +0,0 @@ -export type ModelInfo = { - dimSize: number; -}; - -export const CLIP_MODEL_INFO: Record = { - RN50__openai: { - dimSize: 1024, - }, - RN50__yfcc15m: { - dimSize: 1024, - }, - RN50__cc12m: { - dimSize: 1024, - }, - RN101__openai: { - dimSize: 512, - }, - RN101__yfcc15m: { - dimSize: 512, - }, - RN50x4__openai: { - dimSize: 640, - }, - RN50x16__openai: { - dimSize: 768, - }, - RN50x64__openai: { - dimSize: 1024, - }, - 'ViT-B-32__openai': { - dimSize: 512, - }, - 'ViT-B-32__laion2b_e16': { - dimSize: 512, - }, - 'ViT-B-32__laion400m_e31': { - dimSize: 512, - }, - 'ViT-B-32__laion400m_e32': { - dimSize: 512, - }, - 'ViT-B-32__laion2b-s34b-b79k': { - dimSize: 512, - }, - 'ViT-B-16__openai': { - dimSize: 512, - }, - 'ViT-B-16__laion400m_e31': { - dimSize: 512, - }, - 'ViT-B-16__laion400m_e32': { - dimSize: 512, - }, - 'ViT-B-16-plus-240__laion400m_e31': { - dimSize: 640, - }, - 'ViT-B-16-plus-240__laion400m_e32': { - dimSize: 640, - }, - 'ViT-L-14__openai': { - dimSize: 768, - }, - 'ViT-L-14__laion400m_e31': { - dimSize: 768, - }, - 'ViT-L-14__laion400m_e32': { - dimSize: 768, - }, - 'ViT-L-14__laion2b-s32b-b82k': { - dimSize: 768, - }, - 'ViT-L-14-336__openai': { - dimSize: 768, - }, - 'ViT-L-14-quickgelu__dfn2b': { - dimSize: 768, - }, - 'ViT-H-14__laion2b-s32b-b79k': { - dimSize: 1024, - }, - 'ViT-H-14-quickgelu__dfn5b': { - dimSize: 1024, - }, - 'ViT-H-14-378-quickgelu__dfn5b': { - dimSize: 1024, - }, - 'ViT-g-14__laion2b-s12b-b42k': { - dimSize: 1024, - }, - 'LABSE-Vit-L-14': { - dimSize: 768, - }, - 'XLM-Roberta-Large-Vit-B-32': { - dimSize: 512, - }, - 'XLM-Roberta-Large-Vit-B-16Plus': { - dimSize: 640, - }, - 'XLM-Roberta-Large-Vit-L-14': { - dimSize: 768, - }, - 'XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k': { - dimSize: 1024, - }, - 'nllb-clip-base-siglip__v1': { - dimSize: 768, - }, - 'nllb-clip-large-siglip__v1': { - dimSize: 1152, - }, -}; - -export function cleanModelName(modelName: string): string { - const token = modelName.split('/').at(-1); - if (!token) { - throw new Error(`Invalid model name: ${modelName}`); - } - - return token.replaceAll(':', '_'); -} - -export function getCLIPModelInfo(modelName: string): ModelInfo { - const modelInfo = CLIP_MODEL_INFO[cleanModelName(modelName)]; - if (!modelInfo) { - throw new Error(`Unknown CLIP model: ${modelName}`); - } - - return modelInfo; -} diff --git a/server/src/domain/system-config/system-config.constants.ts b/server/src/domain/system-config/system-config.constants.ts deleted file mode 100644 index 0290472aa0..0000000000 --- a/server/src/domain/system-config/system-config.constants.ts +++ /dev/null @@ -1,27 +0,0 @@ -export const supportedYearTokens = ['y', 'yy']; -export const supportedMonthTokens = ['M', 'MM', 'MMM', 'MMMM']; -export const supportedWeekTokens = ['W', 'WW']; -export const supportedDayTokens = ['d', 'dd']; -export const supportedHourTokens = ['h', 'hh', 'H', 'HH']; -export const supportedMinuteTokens = ['m', 'mm']; -export const supportedSecondTokens = ['s', 'ss', 'SSS']; -export const supportedPresetTokens = [ - '{{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}}', - '{{y}}/{{y}}-{{MM}}/{{filename}}', - '{{y}}/{{y}}-{{WW}}/{{filename}}', - '{{y}}/{{y}}-{{MM}}-{{dd}}/{{assetId}}', - '{{y}}/{{y}}-{{MM}}/{{assetId}}', - '{{y}}/{{y}}-{{WW}}/{{assetId}}', - '{{album}}/{{filename}}', -]; diff --git a/server/src/dtos/job.dto.ts b/server/src/dtos/job.dto.ts index fd463a9b05..ad2ffec114 100644 --- a/server/src/dtos/job.dto.ts +++ b/server/src/dtos/job.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty } from 'class-validator'; -import { JobCommand, QueueName } from 'src/domain/job/job.constants'; +import { JobCommand, QueueName } from 'src/interfaces/job.repository'; import { ValidateBoolean } from 'src/validation'; export class JobIdParamDto { diff --git a/server/src/dtos/server-info.dto.ts b/server/src/dtos/server-info.dto.ts index 33fe830441..cce0de59a6 100644 --- a/server/src/dtos/server-info.dto.ts +++ b/server/src/dtos/server-info.dto.ts @@ -1,8 +1,8 @@ import { ApiProperty, ApiResponseProperty } from '@nestjs/swagger'; import type { DateTime } from 'luxon'; import { FeatureFlags } from 'src/cores/system-config.core'; -import { IVersion, VersionType } from 'src/domain/domain.constant'; import { SystemConfigThemeDto } from 'src/dtos/system-config-theme.dto'; +import { IVersion, VersionType } from 'src/utils/version'; export class ServerPingResponse { @ApiResponseProperty({ type: String, example: 'pong' }) diff --git a/server/src/dtos/system-config-job.dto.ts b/server/src/dtos/system-config-job.dto.ts index 2769da3276..86f4dad1d0 100644 --- a/server/src/dtos/system-config-job.dto.ts +++ b/server/src/dtos/system-config-job.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsInt, IsObject, IsPositive, ValidateNested } from 'class-validator'; -import { ConcurrentQueueName, QueueName } from 'src/domain/job/job.constants'; +import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.repository'; export class JobSettingsDto { @IsInt() diff --git a/server/src/entities/system-config.entity.ts b/server/src/entities/system-config.entity.ts index cf7474417c..23eae493b7 100644 --- a/server/src/entities/system-config.entity.ts +++ b/server/src/entities/system-config.entity.ts @@ -1,4 +1,4 @@ -import { ConcurrentQueueName } from 'src/domain/job/job.constants'; +import { ConcurrentQueueName } from 'src/interfaces/job.repository'; import { Column, Entity, PrimaryColumn } from 'typeorm'; @Entity('system_config') diff --git a/server/src/immich/api-v1/asset/asset-repository.ts b/server/src/immich/api-v1/asset/asset-repository.ts index e7b870239c..af01d4ce02 100644 --- a/server/src/immich/api-v1/asset/asset-repository.ts +++ b/server/src/immich/api-v1/asset/asset-repository.ts @@ -7,7 +7,7 @@ import { CheckExistingAssetsDto } from 'src/immich/api-v1/asset/dto/check-existi import { SearchPropertiesDto } from 'src/immich/api-v1/asset/dto/search-properties.dto'; import { CuratedLocationsResponseDto } from 'src/immich/api-v1/asset/response-dto/curated-locations-response.dto'; import { CuratedObjectsResponseDto } from 'src/immich/api-v1/asset/response-dto/curated-objects-response.dto'; -import { OptionalBetween } from 'src/infra/infra.utils'; +import { OptionalBetween } from 'src/utils/database'; import { In } from 'typeorm/find-options/operator/In.js'; import { Repository } from 'typeorm/repository/Repository.js'; export interface AssetCheck { diff --git a/server/src/immich/api-v1/asset/asset.service.spec.ts b/server/src/immich/api-v1/asset/asset.service.spec.ts index d10590475f..0b465e42c0 100644 --- a/server/src/immich/api-v1/asset/asset.service.spec.ts +++ b/server/src/immich/api-v1/asset/asset.service.spec.ts @@ -1,5 +1,4 @@ import { when } from 'jest-when'; -import { JobName } from 'src/domain/job/job.constants'; import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; import { IAssetRepositoryV1 } from 'src/immich/api-v1/asset/asset-repository'; @@ -7,7 +6,7 @@ import { AssetService } from 'src/immich/api-v1/asset/asset.service'; import { CreateAssetDto } from 'src/immich/api-v1/asset/dto/create-asset.dto'; import { AssetRejectReason, AssetUploadAction } from 'src/immich/api-v1/asset/response-dto/asset-check-response.dto'; import { IAssetRepository } from 'src/interfaces/asset.repository'; -import { IJobRepository } from 'src/interfaces/job.repository'; +import { IJobRepository, JobName } from 'src/interfaces/job.repository'; import { ILibraryRepository } from 'src/interfaces/library.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { IUserRepository } from 'src/interfaces/user.repository'; diff --git a/server/src/immich/api-v1/asset/asset.service.ts b/server/src/immich/api-v1/asset/asset.service.ts index 023017130e..61ad72c568 100644 --- a/server/src/immich/api-v1/asset/asset.service.ts +++ b/server/src/immich/api-v1/asset/asset.service.ts @@ -6,8 +6,6 @@ import { NotFoundException, } from '@nestjs/common'; import { AccessCore, Permission } from 'src/cores/access.core'; -import { mimeTypes } from 'src/domain/domain.constant'; -import { JobName } from 'src/domain/job/job.constants'; import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType } from 'src/entities/asset.entity'; @@ -28,15 +26,16 @@ import { AssetFileUploadResponseDto } from 'src/immich/api-v1/asset/response-dto import { CheckExistingAssetsResponseDto } from 'src/immich/api-v1/asset/response-dto/check-existing-assets-response.dto'; import { CuratedLocationsResponseDto } from 'src/immich/api-v1/asset/response-dto/curated-locations-response.dto'; import { CuratedObjectsResponseDto } from 'src/immich/api-v1/asset/response-dto/curated-objects-response.dto'; -import { ImmichLogger } from 'src/infra/logger'; import { IAccessRepository } from 'src/interfaces/access.repository'; import { IAssetRepository } from 'src/interfaces/asset.repository'; -import { IJobRepository } from 'src/interfaces/job.repository'; +import { IJobRepository, JobName } from 'src/interfaces/job.repository'; import { ILibraryRepository } from 'src/interfaces/library.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { IUserRepository } from 'src/interfaces/user.repository'; import { UploadFile } from 'src/services/asset.service'; -import { CacheControl, ImmichFileResponse, getLivePhotoMotionFilename } from 'src/utils'; +import { CacheControl, ImmichFileResponse, getLivePhotoMotionFilename } from 'src/utils/file'; +import { ImmichLogger } from 'src/utils/logger'; +import { mimeTypes } from 'src/utils/mime-types'; import { QueryFailedError } from 'typeorm'; @Injectable() diff --git a/server/src/immich/app.utils.ts b/server/src/immich/app.utils.ts index f0e4ba6610..46eeccadb9 100644 --- a/server/src/immich/app.utils.ts +++ b/server/src/immich/app.utils.ts @@ -13,12 +13,12 @@ import { writeFileSync } from 'node:fs'; import { access, constants } from 'node:fs/promises'; import path, { isAbsolute } from 'node:path'; import { promisify } from 'node:util'; -import { IMMICH_ACCESS_COOKIE, IMMICH_API_KEY_HEADER, IMMICH_API_KEY_NAME } from 'src/domain/auth/auth.constant'; -import { serverVersion } from 'src/domain/domain.constant'; -import { ImmichLogger } from 'src/infra/logger'; +import { IMMICH_ACCESS_COOKIE, IMMICH_API_KEY_HEADER, IMMICH_API_KEY_NAME, serverVersion } from 'src/constants'; import { ImmichReadStream } from 'src/interfaces/storage.repository'; import { Metadata } from 'src/middleware/auth.guard'; -import { CacheControl, ImmichFileResponse, isConnectionAborted } from 'src/utils'; +import { CacheControl, ImmichFileResponse } from 'src/utils/file'; +import { ImmichLogger } from 'src/utils/logger'; +import { isConnectionAborted } from 'src/utils/misc'; type SendFile = Parameters; type SendFileOptions = SendFile[1]; diff --git a/server/src/infra/sql-generator/sql.logger.ts b/server/src/infra/sql-generator/sql.logger.ts deleted file mode 100644 index 6f3c298c08..0000000000 --- a/server/src/infra/sql-generator/sql.logger.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { format } from 'sql-formatter'; -import { Logger } from 'typeorm'; - -export class SqlLogger implements Logger { - queries: string[] = []; - errors: Array<{ error: string | Error; query: string }> = []; - - clear() { - this.queries = []; - this.errors = []; - } - - logQuery(query: string) { - this.queries.push(format(query, { language: 'postgresql' })); - } - - logQueryError(error: string | Error, query: string) { - this.errors.push({ error, query }); - } - - logQuerySlow() {} - logSchemaBuild() {} - logMigration() {} - log() {} -} diff --git a/server/src/interfaces/asset.repository.ts b/server/src/interfaces/asset.repository.ts index 9bca4a6df8..379357b626 100644 --- a/server/src/interfaces/asset.repository.ts +++ b/server/src/interfaces/asset.repository.ts @@ -4,7 +4,7 @@ import { AssetEntity, AssetType } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; import { ReverseGeocodeResult } from 'src/interfaces/metadata.repository'; import { AssetSearchOptions, SearchExploreItem } from 'src/interfaces/search.repository'; -import { Paginated, PaginationOptions } from 'src/utils'; +import { Paginated, PaginationOptions } from 'src/utils/pagination'; import { FindOptionsRelations, FindOptionsSelect } from 'typeorm'; export type AssetStats = Record; diff --git a/server/src/interfaces/database.repository.ts b/server/src/interfaces/database.repository.ts index e87775c3ec..42342eccc3 100644 --- a/server/src/interfaces/database.repository.ts +++ b/server/src/interfaces/database.repository.ts @@ -1,4 +1,4 @@ -import { Version } from 'src/domain/domain.constant'; +import { Version } from 'src/utils/version'; export enum DatabaseExtension { CUBE = 'cube', diff --git a/server/src/interfaces/job.repository.ts b/server/src/interfaces/job.repository.ts index 023f6a8e9c..6f07fc7525 100644 --- a/server/src/interfaces/job.repository.ts +++ b/server/src/interfaces/job.repository.ts @@ -1,14 +1,139 @@ -import { JobName, QueueName } from 'src/domain/job/job.constants'; -import { - IAssetDeletionJob, - IBaseJob, - IDeferrableJob, - IDeleteFilesJob, - IEntityJob, - ILibraryFileJob, - ILibraryRefreshJob, - ISidecarWriteJob, -} from 'src/domain/job/job.interface'; +export enum QueueName { + THUMBNAIL_GENERATION = 'thumbnailGeneration', + METADATA_EXTRACTION = 'metadataExtraction', + VIDEO_CONVERSION = 'videoConversion', + FACE_DETECTION = 'faceDetection', + FACIAL_RECOGNITION = 'facialRecognition', + SMART_SEARCH = 'smartSearch', + BACKGROUND_TASK = 'backgroundTask', + STORAGE_TEMPLATE_MIGRATION = 'storageTemplateMigration', + MIGRATION = 'migration', + SEARCH = 'search', + SIDECAR = 'sidecar', + LIBRARY = 'library', +} + +export type ConcurrentQueueName = Exclude< + QueueName, + QueueName.STORAGE_TEMPLATE_MIGRATION | QueueName.FACIAL_RECOGNITION +>; + +export enum JobCommand { + START = 'start', + PAUSE = 'pause', + RESUME = 'resume', + EMPTY = 'empty', + CLEAR_FAILED = 'clear-failed', +} + +export enum JobName { + // conversion + QUEUE_VIDEO_CONVERSION = 'queue-video-conversion', + VIDEO_CONVERSION = 'video-conversion', + + // thumbnails + QUEUE_GENERATE_THUMBNAILS = 'queue-generate-thumbnails', + GENERATE_JPEG_THUMBNAIL = 'generate-jpeg-thumbnail', + GENERATE_WEBP_THUMBNAIL = 'generate-webp-thumbnail', + GENERATE_THUMBHASH_THUMBNAIL = 'generate-thumbhash-thumbnail', + GENERATE_PERSON_THUMBNAIL = 'generate-person-thumbnail', + + // metadata + QUEUE_METADATA_EXTRACTION = 'queue-metadata-extraction', + METADATA_EXTRACTION = 'metadata-extraction', + LINK_LIVE_PHOTOS = 'link-live-photos', + + // user + USER_DELETION = 'user-deletion', + USER_DELETE_CHECK = 'user-delete-check', + USER_SYNC_USAGE = 'user-sync-usage', + + // asset + ASSET_DELETION = 'asset-deletion', + ASSET_DELETION_CHECK = 'asset-deletion-check', + + // storage template + STORAGE_TEMPLATE_MIGRATION = 'storage-template-migration', + STORAGE_TEMPLATE_MIGRATION_SINGLE = 'storage-template-migration-single', + + // migration + QUEUE_MIGRATION = 'queue-migration', + MIGRATE_ASSET = 'migrate-asset', + MIGRATE_PERSON = 'migrate-person', + + // facial recognition + PERSON_CLEANUP = 'person-cleanup', + QUEUE_FACE_DETECTION = 'queue-face-detection', + FACE_DETECTION = 'face-detection', + QUEUE_FACIAL_RECOGNITION = 'queue-facial-recognition', + FACIAL_RECOGNITION = 'facial-recognition', + + // library management + LIBRARY_SCAN = 'library-refresh', + LIBRARY_SCAN_ASSET = 'library-refresh-asset', + LIBRARY_REMOVE_OFFLINE = 'library-remove-offline', + LIBRARY_DELETE = 'library-delete', + LIBRARY_QUEUE_SCAN_ALL = 'library-queue-all-refresh', + LIBRARY_QUEUE_CLEANUP = 'library-queue-cleanup', + + // cleanup + DELETE_FILES = 'delete-files', + CLEAN_OLD_AUDIT_LOGS = 'clean-old-audit-logs', + + // smart search + QUEUE_SMART_SEARCH = 'queue-smart-search', + SMART_SEARCH = 'smart-search', + + // XMP sidecars + QUEUE_SIDECAR = 'queue-sidecar', + SIDECAR_DISCOVERY = 'sidecar-discovery', + SIDECAR_SYNC = 'sidecar-sync', + SIDECAR_WRITE = 'sidecar-write', +} + +export const JOBS_ASSET_PAGINATION_SIZE = 1000; + +export interface IBaseJob { + force?: boolean; +} + +export interface IEntityJob extends IBaseJob { + id: string; + source?: 'upload' | 'sidecar-write'; +} + +export interface IAssetDeletionJob extends IEntityJob { + fromExternal?: boolean; +} + +export interface ILibraryFileJob extends IEntityJob { + ownerId: string; + assetPath: string; +} + +export interface ILibraryRefreshJob extends IEntityJob { + refreshModifiedFiles: boolean; + refreshAllFiles: boolean; +} + +export interface IBulkEntityJob extends IBaseJob { + ids: string[]; +} + +export interface IDeleteFilesJob extends IBaseJob { + files: Array; +} + +export interface ISidecarWriteJob extends IEntityJob { + description?: string; + dateTimeOriginal?: string; + latitude?: number; + longitude?: number; +} + +export interface IDeferrableJob extends IEntityJob { + deferred?: boolean; +} export interface JobCounts { active: number; diff --git a/server/src/interfaces/person.repository.ts b/server/src/interfaces/person.repository.ts index cba11fbb97..382bbda22f 100644 --- a/server/src/interfaces/person.repository.ts +++ b/server/src/interfaces/person.repository.ts @@ -1,7 +1,7 @@ import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetEntity } from 'src/entities/asset.entity'; import { PersonEntity } from 'src/entities/person.entity'; -import { Paginated, PaginationOptions } from 'src/utils'; +import { Paginated, PaginationOptions } from 'src/utils/pagination'; import { FindManyOptions, FindOptionsRelations, FindOptionsSelect } from 'typeorm'; export const IPersonRepository = 'IPersonRepository'; diff --git a/server/src/interfaces/search.repository.ts b/server/src/interfaces/search.repository.ts index 29eaa32b73..a4be055493 100644 --- a/server/src/interfaces/search.repository.ts +++ b/server/src/interfaces/search.repository.ts @@ -2,7 +2,7 @@ import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetEntity, AssetType } from 'src/entities/asset.entity'; import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity'; import { SmartInfoEntity } from 'src/entities/smart-info.entity'; -import { Paginated } from 'src/utils'; +import { Paginated } from 'src/utils/pagination'; export const ISearchRepository = 'ISearchRepository'; diff --git a/server/src/middleware/auth.guard.ts b/server/src/middleware/auth.guard.ts index 48befd9a2e..eaa47d013b 100644 --- a/server/src/middleware/auth.guard.ts +++ b/server/src/middleware/auth.guard.ts @@ -9,10 +9,10 @@ import { import { Reflector } from '@nestjs/core'; import { ApiBearerAuth, ApiCookieAuth, ApiOkResponse, ApiQuery, ApiSecurity } from '@nestjs/swagger'; import { Request } from 'express'; -import { IMMICH_API_KEY_NAME } from 'src/domain/auth/auth.constant'; +import { IMMICH_API_KEY_NAME } from 'src/constants'; import { AuthDto } from 'src/dtos/auth.dto'; -import { ImmichLogger } from 'src/infra/logger'; import { AuthService, LoginDetails } from 'src/services/auth.service'; +import { ImmichLogger } from 'src/utils/logger'; import { UAParser } from 'ua-parser-js'; export enum Metadata { diff --git a/server/src/middleware/error.interceptor.ts b/server/src/middleware/error.interceptor.ts index 70c1299224..d354c11077 100644 --- a/server/src/middleware/error.interceptor.ts +++ b/server/src/middleware/error.interceptor.ts @@ -8,8 +8,8 @@ import { } from '@nestjs/common'; import { Observable, catchError, throwError } from 'rxjs'; import { routeToErrorMessage } from 'src/immich/app.utils'; -import { ImmichLogger } from 'src/infra/logger'; -import { isConnectionAborted } from 'src/utils'; +import { ImmichLogger } from 'src/utils/logger'; +import { isConnectionAborted } from 'src/utils/misc'; @Injectable() export class ErrorInterceptor implements NestInterceptor { diff --git a/server/src/middleware/file-upload.interceptor.ts b/server/src/middleware/file-upload.interceptor.ts index f1d5c5c345..53acbefa88 100644 --- a/server/src/middleware/file-upload.interceptor.ts +++ b/server/src/middleware/file-upload.interceptor.ts @@ -7,9 +7,9 @@ import multer, { StorageEngine, diskStorage } from 'multer'; import { createHash, randomUUID } from 'node:crypto'; import { Observable } from 'rxjs'; import { UploadFieldName } from 'src/dtos/asset.dto'; -import { ImmichLogger } from 'src/infra/logger'; import { AuthRequest } from 'src/middleware/auth.guard'; import { AssetService, UploadFile } from 'src/services/asset.service'; +import { ImmichLogger } from 'src/utils/logger'; export enum Route { ASSET = 'asset', diff --git a/server/src/infra/websocket.adapter.ts b/server/src/middleware/websocket.adapter.ts similarity index 100% rename from server/src/infra/websocket.adapter.ts rename to server/src/middleware/websocket.adapter.ts diff --git a/server/src/migrations/1700713871511-UsePgVectors.ts b/server/src/migrations/1700713871511-UsePgVectors.ts index 46d5320ce1..75c85e3e0a 100644 --- a/server/src/migrations/1700713871511-UsePgVectors.ts +++ b/server/src/migrations/1700713871511-UsePgVectors.ts @@ -1,5 +1,5 @@ -import { getCLIPModelInfo } from 'src/domain/smart-info/smart-info.constant'; -import { vectorExt } from 'src/infra/database.config'; +import { vectorExt } from 'src/database.config'; +import { getCLIPModelInfo } from 'src/utils/misc'; import { MigrationInterface, QueryRunner } from 'typeorm'; export class UsePgVectors1700713871511 implements MigrationInterface { diff --git a/server/src/migrations/1700713994428-AddCLIPEmbeddingIndex.ts b/server/src/migrations/1700713994428-AddCLIPEmbeddingIndex.ts index b045ba62b3..6ca4952f16 100644 --- a/server/src/migrations/1700713994428-AddCLIPEmbeddingIndex.ts +++ b/server/src/migrations/1700713994428-AddCLIPEmbeddingIndex.ts @@ -1,4 +1,4 @@ -import { vectorExt } from 'src/infra/database.config'; +import { vectorExt } from 'src/database.config'; import { DatabaseExtension } from 'src/interfaces/database.repository'; import { MigrationInterface, QueryRunner } from 'typeorm'; diff --git a/server/src/migrations/1700714033632-AddFaceEmbeddingIndex.ts b/server/src/migrations/1700714033632-AddFaceEmbeddingIndex.ts index e77ce3b0b4..cc68d689c9 100644 --- a/server/src/migrations/1700714033632-AddFaceEmbeddingIndex.ts +++ b/server/src/migrations/1700714033632-AddFaceEmbeddingIndex.ts @@ -1,4 +1,4 @@ -import { vectorExt } from 'src/infra/database.config'; +import { vectorExt } from 'src/database.config'; import { DatabaseExtension } from 'src/interfaces/database.repository'; import { MigrationInterface, QueryRunner } from 'typeorm'; diff --git a/server/src/infra/sql/access.repository.sql b/server/src/queries/access.repository.sql similarity index 100% rename from server/src/infra/sql/access.repository.sql rename to server/src/queries/access.repository.sql diff --git a/server/src/infra/sql/album.repository.sql b/server/src/queries/album.repository.sql similarity index 100% rename from server/src/infra/sql/album.repository.sql rename to server/src/queries/album.repository.sql diff --git a/server/src/infra/sql/api.key.repository.sql b/server/src/queries/api.key.repository.sql similarity index 100% rename from server/src/infra/sql/api.key.repository.sql rename to server/src/queries/api.key.repository.sql diff --git a/server/src/infra/sql/asset.repository.sql b/server/src/queries/asset.repository.sql similarity index 100% rename from server/src/infra/sql/asset.repository.sql rename to server/src/queries/asset.repository.sql diff --git a/server/src/infra/sql/audit.repository.sql b/server/src/queries/audit.repository.sql similarity index 100% rename from server/src/infra/sql/audit.repository.sql rename to server/src/queries/audit.repository.sql diff --git a/server/src/infra/sql/library.repository.sql b/server/src/queries/library.repository.sql similarity index 100% rename from server/src/infra/sql/library.repository.sql rename to server/src/queries/library.repository.sql diff --git a/server/src/infra/sql/move.repository.sql b/server/src/queries/move.repository.sql similarity index 100% rename from server/src/infra/sql/move.repository.sql rename to server/src/queries/move.repository.sql diff --git a/server/src/infra/sql/partner.repository.sql b/server/src/queries/partner.repository.sql similarity index 100% rename from server/src/infra/sql/partner.repository.sql rename to server/src/queries/partner.repository.sql diff --git a/server/src/infra/sql/person.repository.sql b/server/src/queries/person.repository.sql similarity index 100% rename from server/src/infra/sql/person.repository.sql rename to server/src/queries/person.repository.sql diff --git a/server/src/infra/sql/search.repository.sql b/server/src/queries/search.repository.sql similarity index 100% rename from server/src/infra/sql/search.repository.sql rename to server/src/queries/search.repository.sql diff --git a/server/src/infra/sql/shared.link.repository.sql b/server/src/queries/shared.link.repository.sql similarity index 100% rename from server/src/infra/sql/shared.link.repository.sql rename to server/src/queries/shared.link.repository.sql diff --git a/server/src/infra/sql/system.config.repository.sql b/server/src/queries/system.config.repository.sql similarity index 100% rename from server/src/infra/sql/system.config.repository.sql rename to server/src/queries/system.config.repository.sql diff --git a/server/src/infra/sql/system.metadata.repository.sql b/server/src/queries/system.metadata.repository.sql similarity index 100% rename from server/src/infra/sql/system.metadata.repository.sql rename to server/src/queries/system.metadata.repository.sql diff --git a/server/src/infra/sql/tag.repository.sql b/server/src/queries/tag.repository.sql similarity index 100% rename from server/src/infra/sql/tag.repository.sql rename to server/src/queries/tag.repository.sql diff --git a/server/src/infra/sql/user.repository.sql b/server/src/queries/user.repository.sql similarity index 100% rename from server/src/infra/sql/user.repository.sql rename to server/src/queries/user.repository.sql diff --git a/server/src/infra/sql/user.token.repository.sql b/server/src/queries/user.token.repository.sql similarity index 100% rename from server/src/infra/sql/user.token.repository.sql rename to server/src/queries/user.token.repository.sql diff --git a/server/src/repositories/access.repository.ts b/server/src/repositories/access.repository.ts index 418cf542f9..fc067f2c2b 100644 --- a/server/src/repositories/access.repository.ts +++ b/server/src/repositories/access.repository.ts @@ -9,8 +9,8 @@ import { PartnerEntity } from 'src/entities/partner.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { UserTokenEntity } from 'src/entities/user-token.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { IAccessRepository } from 'src/interfaces/access.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { Brackets, In, Repository } from 'typeorm'; type IActivityAccess = IAccessRepository['activity']; diff --git a/server/src/repositories/activity.repository.ts b/server/src/repositories/activity.repository.ts index bec4a4f175..6d4ef244ed 100644 --- a/server/src/repositories/activity.repository.ts +++ b/server/src/repositories/activity.repository.ts @@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DummyValue, GenerateSql } from 'src/decorators'; import { ActivityEntity } from 'src/entities/activity.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { IActivityRepository } from 'src/interfaces/activity.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { IsNull, Repository } from 'typeorm'; export interface ActivitySearch { diff --git a/server/src/repositories/album.repository.ts b/server/src/repositories/album.repository.ts index 4646ef3d9f..c8ad983256 100644 --- a/server/src/repositories/album.repository.ts +++ b/server/src/repositories/album.repository.ts @@ -1,11 +1,10 @@ import { Injectable } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import _ from 'lodash'; +import { dataSource } from 'src/database.config'; import { Chunked, ChunkedArray, DATABASE_PARAMETER_CHUNK_SIZE, DummyValue, GenerateSql } from 'src/decorators'; import { AlbumEntity } from 'src/entities/album.entity'; import { AssetEntity } from 'src/entities/asset.entity'; -import { dataSource } from 'src/infra/database.config'; -import { Instrumentation } from 'src/infra/instrumentation'; import { AlbumAsset, AlbumAssetCount, @@ -13,7 +12,8 @@ import { AlbumInfoOptions, IAlbumRepository, } from 'src/interfaces/album.repository'; -import { setUnion } from 'src/utils'; +import { Instrumentation } from 'src/utils/instrumentation'; +import { setUnion } from 'src/utils/set'; import { DataSource, FindOptionsOrder, FindOptionsRelations, In, IsNull, Not, Repository } from 'typeorm'; @Instrumentation() diff --git a/server/src/repositories/api-key.repository.ts b/server/src/repositories/api-key.repository.ts index 14a7db9c9e..4ee79f3b45 100644 --- a/server/src/repositories/api-key.repository.ts +++ b/server/src/repositories/api-key.repository.ts @@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DummyValue, GenerateSql } from 'src/decorators'; import { APIKeyEntity } from 'src/entities/api-key.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { IKeyRepository } from 'src/interfaces/api-key.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { Repository } from 'typeorm'; @Instrumentation() diff --git a/server/src/repositories/asset-stack.repository.ts b/server/src/repositories/asset-stack.repository.ts index 9859c5fadc..404908ee09 100644 --- a/server/src/repositories/asset-stack.repository.ts +++ b/server/src/repositories/asset-stack.repository.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { AssetStackEntity } from 'src/entities/asset-stack.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { IAssetStackRepository } from 'src/interfaces/asset-stack.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { Repository } from 'typeorm'; @Instrumentation() diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index 71bef17efb..0fb46a120c 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -8,8 +8,6 @@ import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity'; import { AssetEntity, AssetType } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; import { SmartInfoEntity } from 'src/entities/smart-info.entity'; -import { OptionalBetween, paginate, paginatedBuilder, searchAssetBuilder } from 'src/infra/infra.utils'; -import { Instrumentation } from 'src/infra/instrumentation'; import { AssetBuilderOptions, AssetCreate, @@ -32,7 +30,9 @@ import { WithoutProperty, } from 'src/interfaces/asset.repository'; import { AssetSearchOptions, SearchExploreItem } from 'src/interfaces/search.repository'; -import { Paginated, PaginationMode, PaginationOptions } from 'src/utils'; +import { OptionalBetween, searchAssetBuilder } from 'src/utils/database'; +import { Instrumentation } from 'src/utils/instrumentation'; +import { Paginated, PaginationMode, PaginationOptions, paginate, paginatedBuilder } from 'src/utils/pagination'; import { Brackets, FindOptionsRelations, diff --git a/server/src/repositories/audit.repository.ts b/server/src/repositories/audit.repository.ts index 1d892dab25..ef80819693 100644 --- a/server/src/repositories/audit.repository.ts +++ b/server/src/repositories/audit.repository.ts @@ -1,7 +1,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { AuditEntity } from 'src/entities/audit.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { AuditSearch, IAuditRepository } from 'src/interfaces/audit.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { LessThan, MoreThan, Repository } from 'typeorm'; @Instrumentation() diff --git a/server/src/repositories/communication.repository.ts b/server/src/repositories/communication.repository.ts index 549975c74c..531b63f511 100644 --- a/server/src/repositories/communication.repository.ts +++ b/server/src/repositories/communication.repository.ts @@ -7,8 +7,6 @@ import { WebSocketServer, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; -import { Instrumentation } from 'src/infra/instrumentation'; -import { ImmichLogger } from 'src/infra/logger'; import { ClientEvent, ICommunicationRepository, @@ -18,6 +16,8 @@ import { ServerEvent, } from 'src/interfaces/communication.repository'; import { AuthService } from 'src/services/auth.service'; +import { Instrumentation } from 'src/utils/instrumentation'; +import { ImmichLogger } from 'src/utils/logger'; @Instrumentation() @WebSocketGateway({ diff --git a/server/src/repositories/crypto.repository.ts b/server/src/repositories/crypto.repository.ts index 121943af3b..f7cfa43ff7 100644 --- a/server/src/repositories/crypto.repository.ts +++ b/server/src/repositories/crypto.repository.ts @@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common'; import { compareSync, hash } from 'bcrypt'; import { createHash, randomBytes, randomUUID } from 'node:crypto'; import { createReadStream } from 'node:fs'; -import { Instrumentation } from 'src/infra/instrumentation'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; @Instrumentation() @Injectable() diff --git a/server/src/repositories/database.repository.ts b/server/src/repositories/database.repository.ts index 60586edc5f..4e46327419 100644 --- a/server/src/repositories/database.repository.ts +++ b/server/src/repositories/database.repository.ts @@ -1,10 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectDataSource } from '@nestjs/typeorm'; import AsyncLock from 'async-lock'; -import { Version, VersionType } from 'src/domain/domain.constant'; -import { vectorExt } from 'src/infra/database.config'; -import { Instrumentation } from 'src/infra/instrumentation'; -import { ImmichLogger } from 'src/infra/logger'; +import { vectorExt } from 'src/database.config'; import { DatabaseExtension, DatabaseLock, @@ -14,6 +11,9 @@ import { VectorUpdateResult, extName, } from 'src/interfaces/database.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; +import { ImmichLogger } from 'src/utils/logger'; +import { Version, VersionType } from 'src/utils/version'; import { isValidInteger } from 'src/validation'; import { DataSource, EntityManager, QueryRunner } from 'typeorm'; diff --git a/server/src/repositories/filesystem.provider.ts b/server/src/repositories/filesystem.provider.ts index 1e65f96647..3722893d8b 100644 --- a/server/src/repositories/filesystem.provider.ts +++ b/server/src/repositories/filesystem.provider.ts @@ -4,10 +4,7 @@ import { glob, globStream } from 'fast-glob'; import { constants, createReadStream, existsSync, mkdirSync } from 'node:fs'; import fs from 'node:fs/promises'; import path from 'node:path'; -import { mimeTypes } from 'src/domain/domain.constant'; import { CrawlOptionsDto } from 'src/dtos/library.dto'; -import { Instrumentation } from 'src/infra/instrumentation'; -import { ImmichLogger } from 'src/infra/logger'; import { DiskUsage, IStorageRepository, @@ -16,6 +13,9 @@ import { StorageEventType, WatchEvents, } from 'src/interfaces/storage.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; +import { ImmichLogger } from 'src/utils/logger'; +import { mimeTypes } from 'src/utils/mime-types'; @Instrumentation() export class FilesystemProvider implements IStorageRepository { diff --git a/server/src/repositories/job.repository.ts b/server/src/repositories/job.repository.ts index a7637e0e63..2d07d77480 100644 --- a/server/src/repositories/job.repository.ts +++ b/server/src/repositories/job.repository.ts @@ -6,10 +6,78 @@ import { Job, JobsOptions, Processor, Queue, Worker, WorkerOptions } from 'bullm import { CronJob, CronTime } from 'cron'; import { setTimeout } from 'node:timers/promises'; import { bullConfig } from 'src/config'; -import { JOBS_TO_QUEUE, JobName, QueueName } from 'src/domain/job/job.constants'; -import { Instrumentation } from 'src/infra/instrumentation'; -import { ImmichLogger } from 'src/infra/logger'; -import { IJobRepository, JobCounts, JobItem, QueueCleanType, QueueStatus } from 'src/interfaces/job.repository'; +import { + IJobRepository, + JobCounts, + JobItem, + JobName, + QueueCleanType, + QueueName, + QueueStatus, +} from 'src/interfaces/job.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; +import { ImmichLogger } from 'src/utils/logger'; + +export const JOBS_TO_QUEUE: Record = { + // misc + [JobName.ASSET_DELETION]: QueueName.BACKGROUND_TASK, + [JobName.ASSET_DELETION_CHECK]: QueueName.BACKGROUND_TASK, + [JobName.USER_DELETE_CHECK]: QueueName.BACKGROUND_TASK, + [JobName.USER_DELETION]: QueueName.BACKGROUND_TASK, + [JobName.DELETE_FILES]: QueueName.BACKGROUND_TASK, + [JobName.CLEAN_OLD_AUDIT_LOGS]: QueueName.BACKGROUND_TASK, + [JobName.PERSON_CLEANUP]: QueueName.BACKGROUND_TASK, + [JobName.USER_SYNC_USAGE]: QueueName.BACKGROUND_TASK, + + // conversion + [JobName.QUEUE_VIDEO_CONVERSION]: QueueName.VIDEO_CONVERSION, + [JobName.VIDEO_CONVERSION]: QueueName.VIDEO_CONVERSION, + + // thumbnails + [JobName.QUEUE_GENERATE_THUMBNAILS]: QueueName.THUMBNAIL_GENERATION, + [JobName.GENERATE_JPEG_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, + [JobName.GENERATE_WEBP_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, + [JobName.GENERATE_THUMBHASH_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, + [JobName.GENERATE_PERSON_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, + + // metadata + [JobName.QUEUE_METADATA_EXTRACTION]: QueueName.METADATA_EXTRACTION, + [JobName.METADATA_EXTRACTION]: QueueName.METADATA_EXTRACTION, + [JobName.LINK_LIVE_PHOTOS]: QueueName.METADATA_EXTRACTION, + + // storage template + [JobName.STORAGE_TEMPLATE_MIGRATION]: QueueName.STORAGE_TEMPLATE_MIGRATION, + [JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE]: QueueName.STORAGE_TEMPLATE_MIGRATION, + + // migration + [JobName.QUEUE_MIGRATION]: QueueName.MIGRATION, + [JobName.MIGRATE_ASSET]: QueueName.MIGRATION, + [JobName.MIGRATE_PERSON]: QueueName.MIGRATION, + + // facial recognition + [JobName.QUEUE_FACE_DETECTION]: QueueName.FACE_DETECTION, + [JobName.FACE_DETECTION]: QueueName.FACE_DETECTION, + [JobName.QUEUE_FACIAL_RECOGNITION]: QueueName.FACIAL_RECOGNITION, + [JobName.FACIAL_RECOGNITION]: QueueName.FACIAL_RECOGNITION, + + // smart search + [JobName.QUEUE_SMART_SEARCH]: QueueName.SMART_SEARCH, + [JobName.SMART_SEARCH]: QueueName.SMART_SEARCH, + + // XMP sidecars + [JobName.QUEUE_SIDECAR]: QueueName.SIDECAR, + [JobName.SIDECAR_DISCOVERY]: QueueName.SIDECAR, + [JobName.SIDECAR_SYNC]: QueueName.SIDECAR, + [JobName.SIDECAR_WRITE]: QueueName.SIDECAR, + + // Library management + [JobName.LIBRARY_SCAN_ASSET]: QueueName.LIBRARY, + [JobName.LIBRARY_SCAN]: QueueName.LIBRARY, + [JobName.LIBRARY_DELETE]: QueueName.LIBRARY, + [JobName.LIBRARY_REMOVE_OFFLINE]: QueueName.LIBRARY, + [JobName.LIBRARY_QUEUE_SCAN_ALL]: QueueName.LIBRARY, + [JobName.LIBRARY_QUEUE_CLEANUP]: QueueName.LIBRARY, +}; @Instrumentation() @Injectable() diff --git a/server/src/repositories/library.repository.ts b/server/src/repositories/library.repository.ts index 152f544ce7..ddf63f2285 100644 --- a/server/src/repositories/library.repository.ts +++ b/server/src/repositories/library.repository.ts @@ -3,8 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm'; import { DummyValue, GenerateSql } from 'src/decorators'; import { LibraryStatsResponseDto } from 'src/dtos/library.dto'; import { LibraryEntity, LibraryType } from 'src/entities/library.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { ILibraryRepository } from 'src/interfaces/library.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { IsNull, Not } from 'typeorm'; import { Repository } from 'typeorm/repository/Repository.js'; diff --git a/server/src/repositories/machine-learning.repository.ts b/server/src/repositories/machine-learning.repository.ts index 34f0cdbfc5..75d887943e 100644 --- a/server/src/repositories/machine-learning.repository.ts +++ b/server/src/repositories/machine-learning.repository.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import { readFile } from 'node:fs/promises'; import { CLIPConfig, ModelConfig, RecognitionConfig } from 'src/dtos/model-config.dto'; -import { Instrumentation } from 'src/infra/instrumentation'; import { CLIPMode, DetectFaceResult, @@ -10,6 +9,7 @@ import { TextModelInput, VisionModelInput, } from 'src/interfaces/machine-learning.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; const errorPrefix = 'Machine learning request'; diff --git a/server/src/repositories/media.repository.ts b/server/src/repositories/media.repository.ts index d0f4332b4d..890657e3b7 100644 --- a/server/src/repositories/media.repository.ts +++ b/server/src/repositories/media.repository.ts @@ -4,8 +4,6 @@ import { Writable } from 'node:stream'; import { promisify } from 'node:util'; import sharp from 'sharp'; import { Colorspace } from 'src/entities/system-config.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; -import { ImmichLogger } from 'src/infra/logger'; import { CropOptions, IMediaRepository, @@ -13,7 +11,9 @@ import { TranscodeOptions, VideoInfo, } from 'src/interfaces/media.repository'; -import { handlePromiseError } from 'src/utils'; +import { Instrumentation } from 'src/utils/instrumentation'; +import { ImmichLogger } from 'src/utils/logger'; +import { handlePromiseError } from 'src/utils/misc'; const probe = promisify(ffmpeg.ffprobe); sharp.concurrency(0); diff --git a/server/src/repositories/metadata.repository.ts b/server/src/repositories/metadata.repository.ts index 5da664f14e..1b4dd2b3c3 100644 --- a/server/src/repositories/metadata.repository.ts +++ b/server/src/repositories/metadata.repository.ts @@ -6,21 +6,15 @@ import { getName } from 'i18n-iso-countries'; import { createReadStream, existsSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; import readLine from 'node:readline'; +import { citiesFile, geodataAdmin1Path, geodataAdmin2Path, geodataCities500Path, geodataDatePath } from 'src/constants'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { - citiesFile, - geodataAdmin1Path, - geodataAdmin2Path, - geodataCities500Path, - geodataDatePath, -} from 'src/domain/domain.constant'; import { ExifEntity } from 'src/entities/exif.entity'; import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity'; import { SystemMetadataKey } from 'src/entities/system-metadata.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; -import { ImmichLogger } from 'src/infra/logger'; import { GeoPoint, IMetadataRepository, ImmichTags, ReverseGeocodeResult } from 'src/interfaces/metadata.repository'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; +import { ImmichLogger } from 'src/utils/logger'; import { DataSource, QueryRunner, Repository } from 'typeorm'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js'; diff --git a/server/src/repositories/move.repository.ts b/server/src/repositories/move.repository.ts index e567bcca35..5a17d72a55 100644 --- a/server/src/repositories/move.repository.ts +++ b/server/src/repositories/move.repository.ts @@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DummyValue, GenerateSql } from 'src/decorators'; import { MoveEntity, PathType } from 'src/entities/move.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { IMoveRepository, MoveCreate } from 'src/interfaces/move.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { Repository } from 'typeorm'; @Instrumentation() diff --git a/server/src/repositories/partner.repository.ts b/server/src/repositories/partner.repository.ts index 4c4bbc0ed5..a07241b537 100644 --- a/server/src/repositories/partner.repository.ts +++ b/server/src/repositories/partner.repository.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { PartnerEntity } from 'src/entities/partner.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { IPartnerRepository, PartnerIds } from 'src/interfaces/partner.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { DeepPartial, Repository } from 'typeorm'; @Instrumentation() diff --git a/server/src/repositories/person.repository.ts b/server/src/repositories/person.repository.ts index 6a0008c3dc..805c91d92f 100644 --- a/server/src/repositories/person.repository.ts +++ b/server/src/repositories/person.repository.ts @@ -4,8 +4,6 @@ import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetEntity } from 'src/entities/asset.entity'; import { PersonEntity } from 'src/entities/person.entity'; -import { asVector, paginate } from 'src/infra/infra.utils'; -import { Instrumentation } from 'src/infra/instrumentation'; import { AssetFaceId, IPersonRepository, @@ -15,7 +13,9 @@ import { PersonStatistics, UpdateFacesData, } from 'src/interfaces/person.repository'; -import { Paginated, PaginationOptions } from 'src/utils'; +import { asVector } from 'src/utils/database'; +import { Instrumentation } from 'src/utils/instrumentation'; +import { Paginated, PaginationOptions, paginate } from 'src/utils/pagination'; import { FindManyOptions, FindOptionsRelations, FindOptionsSelect, In, Repository } from 'typeorm'; @Instrumentation() diff --git a/server/src/repositories/search.repository.ts b/server/src/repositories/search.repository.ts index aefb3c9339..8c48337ced 100644 --- a/server/src/repositories/search.repository.ts +++ b/server/src/repositories/search.repository.ts @@ -1,16 +1,12 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { vectorExt } from 'src/database.config'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { getCLIPModelInfo } from 'src/domain/smart-info/smart-info.constant'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetEntity, AssetType } from 'src/entities/asset.entity'; import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity'; import { SmartInfoEntity } from 'src/entities/smart-info.entity'; import { SmartSearchEntity } from 'src/entities/smart-search.entity'; -import { vectorExt } from 'src/infra/database.config'; -import { asVector, paginatedBuilder, searchAssetBuilder } from 'src/infra/infra.utils'; -import { Instrumentation } from 'src/infra/instrumentation'; -import { ImmichLogger } from 'src/infra/logger'; import { DatabaseExtension } from 'src/interfaces/database.repository'; import { AssetSearchOptions, @@ -21,7 +17,11 @@ import { SearchPaginationOptions, SmartSearchOptions, } from 'src/interfaces/search.repository'; -import { Paginated, PaginationMode, PaginationResult } from 'src/utils'; +import { asVector, searchAssetBuilder } from 'src/utils/database'; +import { Instrumentation } from 'src/utils/instrumentation'; +import { ImmichLogger } from 'src/utils/logger'; +import { getCLIPModelInfo } from 'src/utils/misc'; +import { Paginated, PaginationMode, PaginationResult, paginatedBuilder } from 'src/utils/pagination'; import { isValidInteger } from 'src/validation'; import { Repository, SelectQueryBuilder } from 'typeorm'; diff --git a/server/src/repositories/server-info.repository.ts b/server/src/repositories/server-info.repository.ts index fcef0a3962..3c25bc65bc 100644 --- a/server/src/repositories/server-info.repository.ts +++ b/server/src/repositories/server-info.repository.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { Instrumentation } from 'src/infra/instrumentation'; import { GitHubRelease, IServerInfoRepository } from 'src/interfaces/server-info.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; @Instrumentation() @Injectable() diff --git a/server/src/repositories/shared-link.repository.ts b/server/src/repositories/shared-link.repository.ts index 968b6dd731..0e343da5e1 100644 --- a/server/src/repositories/shared-link.repository.ts +++ b/server/src/repositories/shared-link.repository.ts @@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DummyValue, GenerateSql } from 'src/decorators'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { Repository } from 'typeorm'; @Instrumentation() diff --git a/server/src/repositories/system-config.repository.ts b/server/src/repositories/system-config.repository.ts index af6296ce03..ac300fb5d0 100644 --- a/server/src/repositories/system-config.repository.ts +++ b/server/src/repositories/system-config.repository.ts @@ -2,8 +2,8 @@ import { InjectRepository } from '@nestjs/typeorm'; import { readFile } from 'node:fs/promises'; import { Chunked, DummyValue, GenerateSql } from 'src/decorators'; import { SystemConfigEntity } from 'src/entities/system-config.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { In, Repository } from 'typeorm'; @Instrumentation() diff --git a/server/src/repositories/system-metadata.repository.ts b/server/src/repositories/system-metadata.repository.ts index 978dba9bdd..60bf479653 100644 --- a/server/src/repositories/system-metadata.repository.ts +++ b/server/src/repositories/system-metadata.repository.ts @@ -1,7 +1,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { SystemMetadata, SystemMetadataEntity } from 'src/entities/system-metadata.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { Repository } from 'typeorm'; @Instrumentation() diff --git a/server/src/repositories/tag.repository.ts b/server/src/repositories/tag.repository.ts index a2ae74878f..5e2b2bb6d3 100644 --- a/server/src/repositories/tag.repository.ts +++ b/server/src/repositories/tag.repository.ts @@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { AssetEntity } from 'src/entities/asset.entity'; import { TagEntity } from 'src/entities/tag.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { ITagRepository } from 'src/interfaces/tag.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { Repository } from 'typeorm'; @Instrumentation() diff --git a/server/src/repositories/user-token.repository.ts b/server/src/repositories/user-token.repository.ts index 31d6d21901..074aec332d 100644 --- a/server/src/repositories/user-token.repository.ts +++ b/server/src/repositories/user-token.repository.ts @@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DummyValue, GenerateSql } from 'src/decorators'; import { UserTokenEntity } from 'src/entities/user-token.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { IUserTokenRepository } from 'src/interfaces/user-token.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { Repository } from 'typeorm'; @Instrumentation() diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index bd94e71a52..aaadc637f4 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -3,13 +3,13 @@ import { InjectRepository } from '@nestjs/typeorm'; import { DummyValue, GenerateSql } from 'src/decorators'; import { AssetEntity } from 'src/entities/asset.entity'; import { UserEntity } from 'src/entities/user.entity'; -import { Instrumentation } from 'src/infra/instrumentation'; import { IUserRepository, UserFindOptions, UserListFilter, UserStatsQueryResponse, } from 'src/interfaces/user.repository'; +import { Instrumentation } from 'src/utils/instrumentation'; import { IsNull, Not, Repository } from 'typeorm'; @Instrumentation() diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index ea7178bedf..3ae5ebd741 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -21,7 +21,7 @@ import { IAccessRepository } from 'src/interfaces/access.repository'; import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.repository'; import { IAssetRepository } from 'src/interfaces/asset.repository'; import { IUserRepository } from 'src/interfaces/user.repository'; -import { setUnion } from 'src/utils'; +import { setUnion } from 'src/utils/set'; @Injectable() export class AlbumService { diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index 3a6385c03f..c2b6a328e3 100644 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -1,13 +1,12 @@ import { BadRequestException, UnauthorizedException } from '@nestjs/common'; import { when } from 'jest-when'; -import { JobName } from 'src/domain/job/job.constants'; import { mapAsset } from 'src/dtos/asset-response.dto'; import { AssetJobName, AssetStatsResponseDto, UploadFieldName } from 'src/dtos/asset.dto'; import { AssetEntity, AssetType } from 'src/entities/asset.entity'; import { IAssetStackRepository } from 'src/interfaces/asset-stack.repository'; import { AssetStats, IAssetRepository, TimeBucketSize } from 'src/interfaces/asset.repository'; import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; -import { IJobRepository, JobItem } from 'src/interfaces/job.repository'; +import { IJobRepository, JobItem, JobName } from 'src/interfaces/job.repository'; import { IPartnerRepository } from 'src/interfaces/partner.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index 51a587a679..b537eda1cd 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -6,9 +6,6 @@ import sanitize from 'sanitize-filename'; import { AccessCore, Permission } from 'src/cores/access.core'; import { StorageCore, StorageFolder } from 'src/cores/storage.core'; import { SystemConfigCore } from 'src/cores/system-config.core'; -import { mimeTypes } from 'src/domain/domain.constant'; -import { JOBS_ASSET_PAGINATION_SIZE, JobName } from 'src/domain/job/job.constants'; -import { IAssetDeletionJob, ISidecarWriteJob } from 'src/domain/job/job.interface'; import { AssetResponseDto, MemoryLaneResponseDto, @@ -31,17 +28,26 @@ import { UpdateStackParentDto } from 'src/dtos/stack.dto'; import { TimeBucketAssetDto, TimeBucketDto, TimeBucketResponseDto } from 'src/dtos/time-bucket.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { LibraryType } from 'src/entities/library.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { IAccessRepository } from 'src/interfaces/access.repository'; import { IAssetStackRepository } from 'src/interfaces/asset-stack.repository'; import { IAssetRepository, TimeBucketOptions } from 'src/interfaces/asset.repository'; import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; -import { IJobRepository, JobItem, JobStatus } from 'src/interfaces/job.repository'; +import { + IAssetDeletionJob, + IJobRepository, + ISidecarWriteJob, + JOBS_ASSET_PAGINATION_SIZE, + JobItem, + JobName, + JobStatus, +} from 'src/interfaces/job.repository'; import { IPartnerRepository } from 'src/interfaces/partner.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; import { IUserRepository } from 'src/interfaces/user.repository'; -import { usePagination } from 'src/utils'; +import { ImmichLogger } from 'src/utils/logger'; +import { mimeTypes } from 'src/utils/mime-types'; +import { usePagination } from 'src/utils/pagination'; export interface UploadRequest { auth: AuthDto | null; diff --git a/server/src/services/audit.service.ts b/server/src/services/audit.service.ts index b84f3824df..c9e4d16637 100644 --- a/server/src/services/audit.service.ts +++ b/server/src/services/audit.service.ts @@ -1,10 +1,9 @@ import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; import { resolve } from 'node:path'; +import { AUDIT_LOG_MAX_DURATION } from 'src/constants'; import { AccessCore, Permission } from 'src/cores/access.core'; import { StorageCore, StorageFolder } from 'src/cores/storage.core'; -import { AUDIT_LOG_MAX_DURATION } from 'src/domain/domain.constant'; -import { JOBS_ASSET_PAGINATION_SIZE } from 'src/domain/job/job.constants'; import { AuditDeletesDto, AuditDeletesResponseDto, @@ -16,16 +15,16 @@ import { import { AuthDto } from 'src/dtos/auth.dto'; import { DatabaseAction } from 'src/entities/audit.entity'; import { AssetPathType, PersonPathType, UserPathType } from 'src/entities/move.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { IAccessRepository } from 'src/interfaces/access.repository'; import { IAssetRepository } from 'src/interfaces/asset.repository'; import { IAuditRepository } from 'src/interfaces/audit.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; -import { JobStatus } from 'src/interfaces/job.repository'; +import { JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.repository'; import { IPersonRepository } from 'src/interfaces/person.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { IUserRepository } from 'src/interfaces/user.repository'; -import { usePagination } from 'src/utils'; +import { ImmichLogger } from 'src/utils/logger'; +import { usePagination } from 'src/utils/pagination'; @Injectable() export class AuditService { diff --git a/server/src/services/auth.service.spec.ts b/server/src/services/auth.service.spec.ts index dc7565e4dd..a7d212bac1 100644 --- a/server/src/services/auth.service.spec.ts +++ b/server/src/services/auth.service.spec.ts @@ -2,7 +2,7 @@ import { BadRequestException, UnauthorizedException } from '@nestjs/common'; import { IncomingHttpHeaders } from 'node:http'; import { Issuer, generators } from 'openid-client'; import { Socket } from 'socket.io'; -import { AuthType } from 'src/domain/auth/auth.constant'; +import { AuthType } from 'src/constants'; import { AuthDto, SignUpDto } from 'src/dtos/auth.dto'; import { UserEntity } from 'src/entities/user.entity'; import { IKeyRepository } from 'src/interfaces/api-key.repository'; diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index 9ae41a4fc0..0d3d5b118c 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -10,9 +10,6 @@ import cookieParser from 'cookie'; import { DateTime } from 'luxon'; import { IncomingHttpHeaders } from 'node:http'; import { ClientMetadata, Issuer, UserinfoResponse, custom, generators } from 'openid-client'; -import { AccessCore, Permission } from 'src/cores/access.core'; -import { SystemConfigCore } from 'src/cores/system-config.core'; -import { UserCore } from 'src/cores/user.core'; import { AuthType, IMMICH_ACCESS_COOKIE, @@ -21,7 +18,10 @@ import { IMMICH_IS_AUTHENTICATED, LOGIN_URL, MOBILE_REDIRECT, -} from 'src/domain/auth/auth.constant'; +} from 'src/constants'; +import { AccessCore, Permission } from 'src/cores/access.core'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { UserCore } from 'src/cores/user.core'; import { AuthDeviceResponseDto, AuthDto, @@ -39,7 +39,6 @@ import { import { UserResponseDto, mapUser } from 'src/dtos/user.dto'; import { SystemConfig } from 'src/entities/system-config.entity'; import { UserEntity } from 'src/entities/user.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { IAccessRepository } from 'src/interfaces/access.repository'; import { IKeyRepository } from 'src/interfaces/api-key.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; @@ -48,7 +47,8 @@ import { ISharedLinkRepository } from 'src/interfaces/shared-link.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; import { IUserTokenRepository } from 'src/interfaces/user-token.repository'; import { IUserRepository } from 'src/interfaces/user.repository'; -import { HumanReadableSize } from 'src/utils'; +import { HumanReadableSize } from 'src/utils/bytes'; +import { ImmichLogger } from 'src/utils/logger'; export interface LoginDetails { isSecure: boolean; diff --git a/server/src/services/database.service.spec.ts b/server/src/services/database.service.spec.ts index dac857a079..57c1c08211 100644 --- a/server/src/services/database.service.spec.ts +++ b/server/src/services/database.service.spec.ts @@ -1,7 +1,7 @@ -import { Version, VersionType } from 'src/domain/domain.constant'; -import { ImmichLogger } from 'src/infra/logger'; import { DatabaseExtension, IDatabaseRepository, VectorIndex } from 'src/interfaces/database.repository'; import { DatabaseService } from 'src/services/database.service'; +import { ImmichLogger } from 'src/utils/logger'; +import { Version, VersionType } from 'src/utils/version'; import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; describe(DatabaseService.name, () => { diff --git a/server/src/services/database.service.ts b/server/src/services/database.service.ts index 7bb16a4e1a..9f87228b93 100644 --- a/server/src/services/database.service.ts +++ b/server/src/services/database.service.ts @@ -1,6 +1,4 @@ import { Inject, Injectable } from '@nestjs/common'; -import { Version, VersionType } from 'src/domain/domain.constant'; -import { ImmichLogger } from 'src/infra/logger'; import { DatabaseExtension, DatabaseLock, @@ -9,6 +7,8 @@ import { VectorIndex, extName, } from 'src/interfaces/database.repository'; +import { ImmichLogger } from 'src/utils/logger'; +import { Version, VersionType } from 'src/utils/version'; @Injectable() export class DatabaseService { diff --git a/server/src/services/download.service.spec.ts b/server/src/services/download.service.spec.ts index ab83899556..3c4991cd5b 100644 --- a/server/src/services/download.service.spec.ts +++ b/server/src/services/download.service.spec.ts @@ -4,7 +4,7 @@ import { DownloadResponseDto } from 'src/dtos/download.dto'; import { IAssetRepository } from 'src/interfaces/asset.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { DownloadService } from 'src/services/download.service'; -import { CacheControl, ImmichFileResponse } from 'src/utils'; +import { CacheControl, ImmichFileResponse } from 'src/utils/file'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; diff --git a/server/src/services/download.service.ts b/server/src/services/download.service.ts index b03951764b..3ef7dbc4b4 100644 --- a/server/src/services/download.service.ts +++ b/server/src/services/download.service.ts @@ -1,7 +1,6 @@ import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { parse } from 'node:path'; import { AccessCore, Permission } from 'src/cores/access.core'; -import { mimeTypes } from 'src/domain/domain.constant'; import { AssetIdsDto } from 'src/dtos/asset.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { DownloadArchiveInfo, DownloadInfoDto, DownloadResponseDto } from 'src/dtos/download.dto'; @@ -9,7 +8,10 @@ import { AssetEntity } from 'src/entities/asset.entity'; import { IAccessRepository } from 'src/interfaces/access.repository'; import { IAssetRepository } from 'src/interfaces/asset.repository'; import { IStorageRepository, ImmichReadStream } from 'src/interfaces/storage.repository'; -import { CacheControl, HumanReadableSize, ImmichFileResponse, usePagination } from 'src/utils'; +import { HumanReadableSize } from 'src/utils/bytes'; +import { CacheControl, ImmichFileResponse } from 'src/utils/file'; +import { mimeTypes } from 'src/utils/mime-types'; +import { usePagination } from 'src/utils/pagination'; @Injectable() export class DownloadService { diff --git a/server/src/services/job.service.spec.ts b/server/src/services/job.service.spec.ts index 4be9786a2d..13523a1425 100644 --- a/server/src/services/job.service.spec.ts +++ b/server/src/services/job.service.spec.ts @@ -1,10 +1,17 @@ import { BadRequestException } from '@nestjs/common'; import { FeatureFlag, SystemConfigCore } from 'src/cores/system-config.core'; -import { JobCommand, JobName, QueueName } from 'src/domain/job/job.constants'; import { SystemConfig, SystemConfigKey } from 'src/entities/system-config.entity'; import { IAssetRepository } from 'src/interfaces/asset.repository'; import { ICommunicationRepository } from 'src/interfaces/communication.repository'; -import { IJobRepository, JobHandler, JobItem, JobStatus } from 'src/interfaces/job.repository'; +import { + IJobRepository, + JobCommand, + JobHandler, + JobItem, + JobName, + JobStatus, + QueueName, +} from 'src/interfaces/job.repository'; import { IPersonRepository } from 'src/interfaces/person.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; import { JobService } from 'src/services/job.service'; diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index 8650b92d46..e6acd54a32 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -1,15 +1,24 @@ import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { FeatureFlag, SystemConfigCore } from 'src/cores/system-config.core'; -import { ConcurrentQueueName, JobCommand, JobName, QueueName } from 'src/domain/job/job.constants'; import { mapAsset } from 'src/dtos/asset-response.dto'; import { AllJobStatusResponseDto, JobCommandDto, JobStatusDto } from 'src/dtos/job.dto'; import { AssetType } from 'src/entities/asset.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { IAssetRepository } from 'src/interfaces/asset.repository'; import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; -import { IJobRepository, JobHandler, JobItem, JobStatus, QueueCleanType } from 'src/interfaces/job.repository'; +import { + ConcurrentQueueName, + IJobRepository, + JobCommand, + JobHandler, + JobItem, + JobName, + JobStatus, + QueueCleanType, + QueueName, +} from 'src/interfaces/job.repository'; import { IPersonRepository } from 'src/interfaces/person.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { ImmichLogger } from 'src/utils/logger'; @Injectable() export class JobService { diff --git a/server/src/services/library.service.spec.ts b/server/src/services/library.service.spec.ts index 2382d83c20..46cbef3af8 100644 --- a/server/src/services/library.service.spec.ts +++ b/server/src/services/library.service.spec.ts @@ -3,8 +3,6 @@ import { when } from 'jest-when'; import { R_OK } from 'node:constants'; import { Stats } from 'node:fs'; import { SystemConfigCore } from 'src/cores/system-config.core'; -import { JobName } from 'src/domain/job/job.constants'; -import { ILibraryFileJob, ILibraryRefreshJob } from 'src/domain/job/job.interface'; import { mapLibrary } from 'src/dtos/library.dto'; import { AssetType } from 'src/entities/asset.entity'; import { LibraryType } from 'src/entities/library.entity'; @@ -13,7 +11,7 @@ import { UserEntity } from 'src/entities/user.entity'; import { IAssetRepository } from 'src/interfaces/asset.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; import { IDatabaseRepository } from 'src/interfaces/database.repository'; -import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { IJobRepository, ILibraryFileJob, ILibraryRefreshJob, JobName, JobStatus } from 'src/interfaces/job.repository'; import { ILibraryRepository } from 'src/interfaces/library.repository'; import { IStorageRepository, StorageEventType } from 'src/interfaces/storage.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts index f7b298797a..63788d9a7a 100644 --- a/server/src/services/library.service.ts +++ b/server/src/services/library.service.ts @@ -8,9 +8,6 @@ import path, { basename, parse } from 'node:path'; import picomatch from 'picomatch'; import { StorageCore } from 'src/cores/storage.core'; import { SystemConfigCore } from 'src/cores/system-config.core'; -import { mimeTypes } from 'src/domain/domain.constant'; -import { JOBS_ASSET_PAGINATION_SIZE, JobName } from 'src/domain/job/job.constants'; -import { IBaseJob, IEntityJob, ILibraryFileJob, ILibraryRefreshJob } from 'src/domain/job/job.interface'; import { CreateLibraryDto, LibraryResponseDto, @@ -25,16 +22,27 @@ import { } from 'src/dtos/library.dto'; import { AssetType } from 'src/entities/asset.entity'; import { LibraryEntity, LibraryType } from 'src/entities/library.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { IAssetRepository, WithProperty } from 'src/interfaces/asset.repository'; import { InternalEvent, InternalEventMap } from 'src/interfaces/communication.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.repository'; -import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { + IBaseJob, + IEntityJob, + IJobRepository, + ILibraryFileJob, + ILibraryRefreshJob, + JOBS_ASSET_PAGINATION_SIZE, + JobName, + JobStatus, +} from 'src/interfaces/job.repository'; import { ILibraryRepository } from 'src/interfaces/library.repository'; import { IStorageRepository, StorageEventType } from 'src/interfaces/storage.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; -import { handlePromiseError, usePagination } from 'src/utils'; +import { ImmichLogger } from 'src/utils/logger'; +import { mimeTypes } from 'src/utils/mime-types'; +import { handlePromiseError } from 'src/utils/misc'; +import { usePagination } from 'src/utils/pagination'; import { validateCronExpression } from 'src/validation'; const LIBRARY_SCAN_BATCH_SIZE = 5000; diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index eb9407e127..722d2a31c7 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -1,5 +1,4 @@ import { Stats } from 'node:fs'; -import { JobName } from 'src/domain/job/job.constants'; import { AssetType } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; import { @@ -13,7 +12,7 @@ import { } from 'src/entities/system-config.entity'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; -import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.repository'; import { IMediaRepository } from 'src/interfaces/media.repository'; import { IMoveRepository } from 'src/interfaces/move.repository'; import { IPersonRepository } from 'src/interfaces/person.repository'; diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts index 5c217f2a80..107ac7c734 100644 --- a/server/src/services/media.service.ts +++ b/server/src/services/media.service.ts @@ -1,18 +1,6 @@ import { Inject, Injectable, UnsupportedMediaTypeException } from '@nestjs/common'; import { StorageCore, StorageFolder } from 'src/cores/storage.core'; import { SystemConfigCore } from 'src/cores/system-config.core'; -import { JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from 'src/domain/job/job.constants'; -import { IBaseJob, IEntityJob } from 'src/domain/job/job.interface'; -import { - H264Config, - HEVCConfig, - NVENCConfig, - QSVConfig, - RKMPPConfig, - ThumbnailConfig, - VAAPIConfig, - VP9Config, -} from 'src/domain/media/media.util'; import { SystemConfigFFmpegDto } from 'src/dtos/system-config-ffmpeg.dto'; import { AssetEntity, AssetType } from 'src/entities/asset.entity'; import { AssetPathType } from 'src/entities/move.entity'; @@ -24,10 +12,18 @@ import { TranscodeTarget, VideoCodec, } from 'src/entities/system-config.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; -import { IJobRepository, JobItem, JobStatus } from 'src/interfaces/job.repository'; +import { + IBaseJob, + IEntityJob, + IJobRepository, + JOBS_ASSET_PAGINATION_SIZE, + JobItem, + JobName, + JobStatus, + QueueName, +} from 'src/interfaces/job.repository'; import { AudioStreamInfo, IMediaRepository, @@ -38,7 +34,18 @@ import { IMoveRepository } from 'src/interfaces/move.repository'; import { IPersonRepository } from 'src/interfaces/person.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; -import { usePagination } from 'src/utils'; +import { ImmichLogger } from 'src/utils/logger'; +import { + H264Config, + HEVCConfig, + NVENCConfig, + QSVConfig, + RKMPPConfig, + ThumbnailConfig, + VAAPIConfig, + VP9Config, +} from 'src/utils/media'; +import { usePagination } from 'src/utils/pagination'; @Injectable() export class MediaService { diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index a235aca040..29347f70c3 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -3,7 +3,6 @@ import { when } from 'jest-when'; import { randomBytes } from 'node:crypto'; import { Stats } from 'node:fs'; import { constants } from 'node:fs/promises'; -import { JobName } from 'src/domain/job/job.constants'; import { AssetType } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; import { SystemConfigKey } from 'src/entities/system-config.entity'; @@ -12,7 +11,7 @@ import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.reposito import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; import { IDatabaseRepository } from 'src/interfaces/database.repository'; -import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.repository'; import { IMediaRepository } from 'src/interfaces/media.repository'; import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.repository'; import { IMoveRepository } from 'src/interfaces/move.repository'; diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 0e949626cd..7f9e389bb1 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -8,24 +8,32 @@ import path from 'node:path'; import { Subscription } from 'rxjs'; import { StorageCore } from 'src/cores/storage.core'; import { FeatureFlag, SystemConfigCore } from 'src/cores/system-config.core'; -import { JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from 'src/domain/job/job.constants'; -import { IBaseJob, IEntityJob, ISidecarWriteJob } from 'src/domain/job/job.interface'; import { AssetEntity, AssetType } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { IAlbumRepository } from 'src/interfaces/album.repository'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.repository'; -import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { + IBaseJob, + IEntityJob, + IJobRepository, + ISidecarWriteJob, + JOBS_ASSET_PAGINATION_SIZE, + JobName, + JobStatus, + QueueName, +} from 'src/interfaces/job.repository'; import { IMediaRepository } from 'src/interfaces/media.repository'; import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.repository'; import { IMoveRepository } from 'src/interfaces/move.repository'; import { IPersonRepository } from 'src/interfaces/person.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; -import { handlePromiseError, usePagination } from 'src/utils'; +import { ImmichLogger } from 'src/utils/logger'; +import { handlePromiseError } from 'src/utils/misc'; +import { usePagination } from 'src/utils/pagination'; /** look for a date from these tags (in order) */ const EXIF_DATE_TAGS: Array = [ diff --git a/server/src/services/person.service.spec.ts b/server/src/services/person.service.spec.ts index 7abc9f3d9a..ae391fa2b6 100644 --- a/server/src/services/person.service.spec.ts +++ b/server/src/services/person.service.spec.ts @@ -1,12 +1,11 @@ import { BadRequestException, NotFoundException } from '@nestjs/common'; -import { JobName } from 'src/domain/job/job.constants'; import { BulkIdErrorReason } from 'src/dtos/asset-ids.response.dto'; import { PersonResponseDto, mapFaces, mapPerson } from 'src/dtos/person.dto'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { Colorspace, SystemConfigKey } from 'src/entities/system-config.entity'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; -import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.repository'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.repository'; import { IMediaRepository } from 'src/interfaces/media.repository'; import { IMoveRepository } from 'src/interfaces/move.repository'; @@ -15,7 +14,7 @@ import { FaceSearchResult, ISearchRepository } from 'src/interfaces/search.repos import { IStorageRepository } from 'src/interfaces/storage.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; import { PersonService } from 'src/services/person.service'; -import { CacheControl, ImmichFileResponse } from 'src/utils'; +import { CacheControl, ImmichFileResponse } from 'src/utils/file'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { faceStub } from 'test/fixtures/face.stub'; diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index 7da482966f..99eae8c065 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -1,11 +1,8 @@ import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { FACE_THUMBNAIL_SIZE } from 'src/constants'; import { AccessCore, Permission } from 'src/cores/access.core'; import { StorageCore } from 'src/cores/storage.core'; import { SystemConfigCore } from 'src/cores/system-config.core'; -import { mimeTypes } from 'src/domain/domain.constant'; -import { JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from 'src/domain/job/job.constants'; -import { IBaseJob, IDeferrableJob, IEntityJob } from 'src/domain/job/job.interface'; -import { FACE_THUMBNAIL_SIZE } from 'src/domain/media/media.constant'; import { BulkIdErrorReason, BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto'; import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; @@ -26,11 +23,20 @@ import { } from 'src/dtos/person.dto'; import { PersonPathType } from 'src/entities/move.entity'; import { PersonEntity } from 'src/entities/person.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { IAccessRepository } from 'src/interfaces/access.repository'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; -import { IJobRepository, JobItem, JobStatus } from 'src/interfaces/job.repository'; +import { + IBaseJob, + IDeferrableJob, + IEntityJob, + IJobRepository, + JOBS_ASSET_PAGINATION_SIZE, + JobItem, + JobName, + JobStatus, + QueueName, +} from 'src/interfaces/job.repository'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.repository'; import { CropOptions, IMediaRepository } from 'src/interfaces/media.repository'; import { IMoveRepository } from 'src/interfaces/move.repository'; @@ -38,7 +44,10 @@ import { IPersonRepository, UpdateFacesData } from 'src/interfaces/person.reposi import { ISearchRepository } from 'src/interfaces/search.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; -import { CacheControl, ImmichFileResponse, usePagination } from 'src/utils'; +import { CacheControl, ImmichFileResponse } from 'src/utils/file'; +import { ImmichLogger } from 'src/utils/logger'; +import { mimeTypes } from 'src/utils/mime-types'; +import { usePagination } from 'src/utils/pagination'; import { IsNull } from 'typeorm'; @Injectable() diff --git a/server/src/services/server-info.service.spec.ts b/server/src/services/server-info.service.spec.ts index 0db7597b82..fe678dbef2 100644 --- a/server/src/services/server-info.service.spec.ts +++ b/server/src/services/server-info.service.spec.ts @@ -1,4 +1,4 @@ -import { serverVersion } from 'src/domain/domain.constant'; +import { serverVersion } from 'src/constants'; import { SystemMetadataKey } from 'src/entities/system-metadata.entity'; import { ICommunicationRepository } from 'src/interfaces/communication.repository'; import { IServerInfoRepository } from 'src/interfaces/server-info.repository'; diff --git a/server/src/services/server-info.service.ts b/server/src/services/server-info.service.ts index a76ae4022b..e0a62bdc74 100644 --- a/server/src/services/server-info.service.ts +++ b/server/src/services/server-info.service.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; +import { isDev, serverVersion } from 'src/constants'; import { StorageCore, StorageFolder } from 'src/cores/storage.core'; import { SystemConfigCore } from 'src/cores/system-config.core'; -import { Version, isDev, mimeTypes, serverVersion } from 'src/domain/domain.constant'; import { ServerConfigDto, ServerFeaturesDto, @@ -13,14 +13,16 @@ import { UsageByUserDto, } from 'src/dtos/server-info.dto'; import { SystemMetadataKey } from 'src/entities/system-metadata.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; import { IServerInfoRepository } from 'src/interfaces/server-info.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.repository'; import { IUserRepository, UserStatsQueryResponse } from 'src/interfaces/user.repository'; -import { asHumanReadable } from 'src/utils'; +import { asHumanReadable } from 'src/utils/bytes'; +import { ImmichLogger } from 'src/utils/logger'; +import { mimeTypes } from 'src/utils/mime-types'; +import { Version } from 'src/utils/version'; @Injectable() export class ServerInfoService { diff --git a/server/src/services/shared-link.service.ts b/server/src/services/shared-link.service.ts index a84a1f1206..44b1f91e5d 100644 --- a/server/src/services/shared-link.service.ts +++ b/server/src/services/shared-link.service.ts @@ -16,7 +16,7 @@ import { SharedLinkEntity, SharedLinkType } from 'src/entities/shared-link.entit import { IAccessRepository } from 'src/interfaces/access.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.repository'; -import { OpenGraphTags } from 'src/utils'; +import { OpenGraphTags } from 'src/utils/misc'; @Injectable() export class SharedLinkService { diff --git a/server/src/services/smart-info.service.spec.ts b/server/src/services/smart-info.service.spec.ts index a1b8943f2e..b74211da34 100644 --- a/server/src/services/smart-info.service.spec.ts +++ b/server/src/services/smart-info.service.spec.ts @@ -1,14 +1,13 @@ -import { JobName } from 'src/domain/job/job.constants'; -import { cleanModelName, getCLIPModelInfo } from 'src/domain/smart-info/smart-info.constant'; import { AssetEntity } from 'src/entities/asset.entity'; import { SystemConfigKey } from 'src/entities/system-config.entity'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; import { IDatabaseRepository } from 'src/interfaces/database.repository'; -import { IJobRepository } from 'src/interfaces/job.repository'; +import { IJobRepository, JobName } from 'src/interfaces/job.repository'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.repository'; import { ISearchRepository } from 'src/interfaces/search.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; import { SmartInfoService } from 'src/services/smart-info.service'; +import { getCLIPModelInfo } from 'src/utils/misc'; import { assetStub } from 'test/fixtures/asset.stub'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; @@ -124,16 +123,14 @@ describe(SmartInfoService.name, () => { }); }); - describe('cleanModelName', () => { - it('should clean name', () => { - expect(cleanModelName('ViT-B-32::openai')).toEqual('ViT-B-32__openai'); - expect(cleanModelName('M-CLIP/XLM-Roberta-Large-Vit-L-14')).toEqual('XLM-Roberta-Large-Vit-L-14'); - }); - }); - describe('getCLIPModelInfo', () => { it('should return the model info', () => { expect(getCLIPModelInfo('ViT-B-32__openai')).toEqual({ dimSize: 512 }); + expect(getCLIPModelInfo('M-CLIP/XLM-Roberta-Large-Vit-L-14')).toEqual({ dimSize: 768 }); + }); + + it('should clean the model name', () => { + expect(getCLIPModelInfo('ViT-B-32::openai')).toEqual({ dimSize: 512 }); }); it('should throw an error if the model is not present', () => { diff --git a/server/src/services/smart-info.service.ts b/server/src/services/smart-info.service.ts index a7d4700086..d686cd56da 100644 --- a/server/src/services/smart-info.service.ts +++ b/server/src/services/smart-info.service.ts @@ -1,15 +1,21 @@ import { Inject, Injectable } from '@nestjs/common'; import { SystemConfigCore } from 'src/cores/system-config.core'; -import { JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from 'src/domain/job/job.constants'; -import { IBaseJob, IEntityJob } from 'src/domain/job/job.interface'; -import { ImmichLogger } from 'src/infra/logger'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.repository'; -import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { + IBaseJob, + IEntityJob, + IJobRepository, + JOBS_ASSET_PAGINATION_SIZE, + JobName, + JobStatus, + QueueName, +} from 'src/interfaces/job.repository'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.repository'; import { ISearchRepository } from 'src/interfaces/search.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; -import { usePagination } from 'src/utils'; +import { ImmichLogger } from 'src/utils/logger'; +import { usePagination } from 'src/utils/pagination'; @Injectable() export class SmartInfoService { diff --git a/server/src/services/storage-template.service.ts b/server/src/services/storage-template.service.ts index b4af03a9f7..43c6eb09ea 100644 --- a/server/src/services/storage-template.service.ts +++ b/server/src/services/storage-template.service.ts @@ -4,10 +4,6 @@ import handlebar from 'handlebars'; import { DateTime } from 'luxon'; import path from 'node:path'; import sanitize from 'sanitize-filename'; -import { StorageCore, StorageFolder } from 'src/cores/storage.core'; -import { SystemConfigCore } from 'src/cores/system-config.core'; -import { JOBS_ASSET_PAGINATION_SIZE } from 'src/domain/job/job.constants'; -import { IEntityJob } from 'src/domain/job/job.interface'; import { supportedDayTokens, supportedHourTokens, @@ -16,23 +12,26 @@ import { supportedSecondTokens, supportedWeekTokens, supportedYearTokens, -} from 'src/domain/system-config/system-config.constants'; +} from 'src/constants'; +import { StorageCore, StorageFolder } from 'src/cores/storage.core'; +import { SystemConfigCore } from 'src/cores/system-config.core'; import { AssetEntity, AssetType } from 'src/entities/asset.entity'; import { AssetPathType } from 'src/entities/move.entity'; import { SystemConfig } from 'src/entities/system-config.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { IAlbumRepository } from 'src/interfaces/album.repository'; import { IAssetRepository } from 'src/interfaces/asset.repository'; import { InternalEvent, InternalEventMap } from 'src/interfaces/communication.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.repository'; -import { JobStatus } from 'src/interfaces/job.repository'; +import { IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.repository'; import { IMoveRepository } from 'src/interfaces/move.repository'; import { IPersonRepository } from 'src/interfaces/person.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; import { IUserRepository } from 'src/interfaces/user.repository'; -import { getLivePhotoMotionFilename, usePagination } from 'src/utils'; +import { getLivePhotoMotionFilename } from 'src/utils/file'; +import { ImmichLogger } from 'src/utils/logger'; +import { usePagination } from 'src/utils/pagination'; export interface MoveAssetMetadata { storageLabel: string | null; diff --git a/server/src/services/storage.service.ts b/server/src/services/storage.service.ts index 9df3dd7784..22cac00403 100644 --- a/server/src/services/storage.service.ts +++ b/server/src/services/storage.service.ts @@ -1,9 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { StorageCore, StorageFolder } from 'src/cores/storage.core'; -import { IDeleteFilesJob } from 'src/domain/job/job.interface'; -import { ImmichLogger } from 'src/infra/logger'; -import { JobStatus } from 'src/interfaces/job.repository'; +import { IDeleteFilesJob, JobStatus } from 'src/interfaces/job.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ImmichLogger } from 'src/utils/logger'; @Injectable() export class StorageService { diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts index b5b5875d71..d7896e87ea 100644 --- a/server/src/services/system-config.service.spec.ts +++ b/server/src/services/system-config.service.spec.ts @@ -1,6 +1,5 @@ import { BadRequestException } from '@nestjs/common'; import { defaults } from 'src/cores/system-config.core'; -import { QueueName } from 'src/domain/job/job.constants'; import { AudioCodec, CQMode, @@ -14,11 +13,12 @@ import { TranscodePolicy, VideoCodec, } from 'src/entities/system-config.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { ICommunicationRepository, ServerEvent } from 'src/interfaces/communication.repository'; +import { QueueName } from 'src/interfaces/job.repository'; import { ISearchRepository } from 'src/interfaces/search.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; import { SystemConfigService } from 'src/services/system-config.service'; +import { ImmichLogger } from 'src/utils/logger'; import { newCommunicationRepositoryMock } from 'test/repositories/communication.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; diff --git a/server/src/services/system-config.service.ts b/server/src/services/system-config.service.ts index c5837dca27..acb8b13fb8 100644 --- a/server/src/services/system-config.service.ts +++ b/server/src/services/system-config.service.ts @@ -2,7 +2,6 @@ import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { instanceToPlain } from 'class-transformer'; import _ from 'lodash'; -import { SystemConfigCore } from 'src/cores/system-config.core'; import { supportedDayTokens, supportedHourTokens, @@ -12,11 +11,11 @@ import { supportedSecondTokens, supportedWeekTokens, supportedYearTokens, -} from 'src/domain/system-config/system-config.constants'; +} from 'src/constants'; +import { SystemConfigCore } from 'src/cores/system-config.core'; import { SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config-storage-template.dto'; import { SystemConfigDto, mapConfig } from 'src/dtos/system-config.dto'; import { LogLevel, SystemConfig } from 'src/entities/system-config.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { ClientEvent, ICommunicationRepository, @@ -26,6 +25,7 @@ import { } from 'src/interfaces/communication.repository'; import { ISearchRepository } from 'src/interfaces/search.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { ImmichLogger } from 'src/utils/logger'; @Injectable() export class SystemConfigService { diff --git a/server/src/services/trash.service.spec.ts b/server/src/services/trash.service.spec.ts index 09f668e6fd..03e87a8d02 100644 --- a/server/src/services/trash.service.spec.ts +++ b/server/src/services/trash.service.spec.ts @@ -1,8 +1,7 @@ import { BadRequestException } from '@nestjs/common'; -import { JobName } from 'src/domain/job/job.constants'; import { IAssetRepository } from 'src/interfaces/asset.repository'; import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; -import { IJobRepository } from 'src/interfaces/job.repository'; +import { IJobRepository, JobName } from 'src/interfaces/job.repository'; import { TrashService } from 'src/services/trash.service'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; diff --git a/server/src/services/trash.service.ts b/server/src/services/trash.service.ts index 9bece663b8..61de730975 100644 --- a/server/src/services/trash.service.ts +++ b/server/src/services/trash.service.ts @@ -1,14 +1,13 @@ import { Inject } from '@nestjs/common'; import { DateTime } from 'luxon'; import { AccessCore, Permission } from 'src/cores/access.core'; -import { JOBS_ASSET_PAGINATION_SIZE, JobName } from 'src/domain/job/job.constants'; import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { IAccessRepository } from 'src/interfaces/access.repository'; import { IAssetRepository } from 'src/interfaces/asset.repository'; import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; -import { IJobRepository } from 'src/interfaces/job.repository'; -import { usePagination } from 'src/utils'; +import { IJobRepository, JOBS_ASSET_PAGINATION_SIZE, JobName } from 'src/interfaces/job.repository'; +import { usePagination } from 'src/utils/pagination'; export class TrashService { private access: AccessCore; diff --git a/server/src/services/user.service.spec.ts b/server/src/services/user.service.spec.ts index 51457b201e..6a639003df 100644 --- a/server/src/services/user.service.spec.ts +++ b/server/src/services/user.service.spec.ts @@ -5,18 +5,17 @@ import { NotFoundException, } from '@nestjs/common'; import { when } from 'jest-when'; -import { JobName } from 'src/domain/job/job.constants'; import { UpdateUserDto, mapUser } from 'src/dtos/user.dto'; import { UserEntity, UserStatus } from 'src/entities/user.entity'; import { IAlbumRepository } from 'src/interfaces/album.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; -import { IJobRepository } from 'src/interfaces/job.repository'; +import { IJobRepository, JobName } from 'src/interfaces/job.repository'; import { ILibraryRepository } from 'src/interfaces/library.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; import { IUserRepository } from 'src/interfaces/user.repository'; import { UserService } from 'src/services/user.service'; -import { CacheControl, ImmichFileResponse } from 'src/utils'; +import { CacheControl, ImmichFileResponse } from 'src/utils/file'; import { authStub } from 'test/fixtures/auth.stub'; import { systemConfigStub } from 'test/fixtures/system-config.stub'; import { userStub } from 'test/fixtures/user.stub'; diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index ef742123ff..e4d8f11c49 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -4,21 +4,19 @@ import { randomBytes } from 'node:crypto'; import { StorageCore, StorageFolder } from 'src/cores/storage.core'; import { SystemConfigCore } from 'src/cores/system-config.core'; import { UserCore } from 'src/cores/user.core'; -import { JobName } from 'src/domain/job/job.constants'; -import { IEntityJob } from 'src/domain/job/job.interface'; import { AuthDto } from 'src/dtos/auth.dto'; import { CreateProfileImageResponseDto, mapCreateProfileImageResponse } from 'src/dtos/user-profile.dto'; import { CreateUserDto, DeleteUserDto, UpdateUserDto, UserResponseDto, mapUser } from 'src/dtos/user.dto'; import { UserEntity, UserStatus } from 'src/entities/user.entity'; -import { ImmichLogger } from 'src/infra/logger'; import { IAlbumRepository } from 'src/interfaces/album.repository'; import { ICryptoRepository } from 'src/interfaces/crypto.repository'; -import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { IEntityJob, IJobRepository, JobName, JobStatus } from 'src/interfaces/job.repository'; import { ILibraryRepository } from 'src/interfaces/library.repository'; import { IStorageRepository } from 'src/interfaces/storage.repository'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; import { IUserRepository, UserFindOptions } from 'src/interfaces/user.repository'; -import { CacheControl, ImmichFileResponse } from 'src/utils'; +import { CacheControl, ImmichFileResponse } from 'src/utils/file'; +import { ImmichLogger } from 'src/utils/logger'; @Injectable() export class UserService { diff --git a/server/src/utils.spec.ts b/server/src/utils.spec.ts deleted file mode 100644 index c5ae5f6e57..0000000000 --- a/server/src/utils.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { isDecimalNumber, isNumberInRange, parseLatitude, parseLongitude, toNumberOrNull } from 'src/utils'; - -describe('checks if a number is a decimal number', () => { - it('returns false for non-decimal numbers', () => { - expect(isDecimalNumber(Number.NaN)).toBe(false); - expect(isDecimalNumber(Number.POSITIVE_INFINITY)).toBe(false); - expect(isDecimalNumber(Number.NEGATIVE_INFINITY)).toBe(false); - }); - - it('returns true for decimal numbers', () => { - expect(isDecimalNumber(0)).toBe(true); - expect(isDecimalNumber(-0)).toBe(true); - expect(isDecimalNumber(10.123_45)).toBe(true); - expect(isDecimalNumber(Number.MAX_VALUE)).toBe(true); - expect(isDecimalNumber(Number.MIN_VALUE)).toBe(true); - }); -}); - -describe('checks if a number is within a range', () => { - it('returns false for numbers outside the range', () => { - expect(isNumberInRange(0, 10, 10)).toBe(false); - expect(isNumberInRange(0.01, 10, 10)).toBe(false); - expect(isNumberInRange(50.1, 0, 50)).toBe(false); - }); - - it('returns true for numbers inside the range', () => { - expect(isNumberInRange(0, 0, 50)).toBe(true); - expect(isNumberInRange(50, 0, 50)).toBe(true); - expect(isNumberInRange(-50.123_45, -50.123_45, 0)).toBe(true); - }); -}); - -describe('converts input to a number or null', () => { - it('returns null for invalid inputs', () => { - expect(toNumberOrNull(null)).toBeNull(); - // eslint-disable-next-line unicorn/no-useless-undefined - expect(toNumberOrNull(undefined)).toBeNull(); - expect(toNumberOrNull('')).toBeNull(); - expect(toNumberOrNull(Number.NaN)).toBeNull(); - }); - - it('returns a number for valid inputs', () => { - expect(toNumberOrNull(0)).toBeCloseTo(0); - expect(toNumberOrNull('0')).toBeCloseTo(0); - expect(toNumberOrNull('-123.45')).toBeCloseTo(-123.45); - }); -}); - -describe('parsing latitude from string input', () => { - it('returns null for invalid inputs', () => { - expect(parseLatitude('')).toBeNull(); - expect(parseLatitude('NaN')).toBeNull(); - expect(parseLatitude('Infinity')).toBeNull(); - expect(parseLatitude('-Infinity')).toBeNull(); - expect(parseLatitude('90.001')).toBeNull(); - expect(parseLatitude(-90.000_001)).toBeNull(); - expect(parseLatitude('1000')).toBeNull(); - expect(parseLatitude(-1000)).toBeNull(); - }); - - it('returns the numeric coordinate for valid inputs', () => { - expect(parseLatitude('90')).toBeCloseTo(90); - expect(parseLatitude('-90')).toBeCloseTo(-90); - expect(parseLatitude(89.999_999)).toBeCloseTo(89.999_999); - expect(parseLatitude('-89.9')).toBeCloseTo(-89.9); - expect(parseLatitude(0)).toBeCloseTo(0); - expect(parseLatitude('-0.0')).toBeCloseTo(-0); - }); -}); - -describe('parsing latitude from null input', () => { - it('returns null for null input', () => { - expect(parseLatitude(null)).toBeNull(); - }); -}); - -describe('parsing longitude from string input', () => { - it('returns null for invalid inputs', () => { - expect(parseLongitude('')).toBeNull(); - expect(parseLongitude('NaN')).toBeNull(); - expect(parseLongitude(Number.POSITIVE_INFINITY)).toBeNull(); - expect(parseLongitude('-Infinity')).toBeNull(); - expect(parseLongitude('180.001')).toBeNull(); - expect(parseLongitude('-180.000001')).toBeNull(); - expect(parseLongitude(1000)).toBeNull(); - expect(parseLongitude('-1000')).toBeNull(); - }); - - it('returns the numeric coordinate for valid inputs', () => { - expect(parseLongitude(180)).toBeCloseTo(180); - expect(parseLongitude('-180')).toBeCloseTo(-180); - expect(parseLongitude('179.999999')).toBeCloseTo(179.999_999); - expect(parseLongitude(-179.9)).toBeCloseTo(-179.9); - expect(parseLongitude('0')).toBeCloseTo(0); - expect(parseLongitude('-0.0')).toBeCloseTo(-0); - }); -}); - -describe('parsing longitude from null input', () => { - it('returns null for null input', () => { - expect(parseLongitude(null)).toBeNull(); - }); -}); diff --git a/server/src/utils.ts b/server/src/utils.ts deleted file mode 100644 index 863dde7ca8..0000000000 --- a/server/src/utils.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { basename, extname } from 'node:path'; -import { ImmichLogger } from 'src/infra/logger'; - -const KiB = Math.pow(1024, 1); -const MiB = Math.pow(1024, 2); -const GiB = Math.pow(1024, 3); -const TiB = Math.pow(1024, 4); -const PiB = Math.pow(1024, 5); - -export const HumanReadableSize = { KiB, MiB, GiB, TiB, PiB }; - -export function asHumanReadable(bytes: number, precision = 1): string { - const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']; - - let magnitude = 0; - let remainder = bytes; - while (remainder >= 1024) { - if (magnitude + 1 < units.length) { - magnitude++; - remainder /= 1024; - } else { - break; - } - } - - return `${remainder.toFixed(magnitude == 0 ? 0 : precision)} ${units[magnitude]}`; -} - -export const isConnectionAborted = (error: Error | any) => error.code === 'ECONNABORTED'; - -export function isDecimalNumber(number_: number): boolean { - return !Number.isNaN(number_) && Number.isFinite(number_); -} - -/** - * Check if `num` is a valid number and is between `start` and `end` (inclusive) - */ -export function isNumberInRange(number_: number, start: number, end: number): boolean { - return isDecimalNumber(number_) && number_ >= start && number_ <= end; -} - -export function toNumberOrNull(input: number | string | null | undefined): number | null { - if (input === null || input === undefined) { - return null; - } - - const number_ = typeof input === 'string' ? Number.parseFloat(input) : input; - return isDecimalNumber(number_) ? number_ : null; -} - -export function parseLatitude(input: string | number | null): number | null { - if (input === null) { - return null; - } - const latitude = typeof input === 'string' ? Number.parseFloat(input) : input; - - if (isNumberInRange(latitude, -90, 90)) { - return latitude; - } - return null; -} - -export function parseLongitude(input: string | number | null): number | null { - if (input === null) { - return null; - } - - const longitude = typeof input === 'string' ? Number.parseFloat(input) : input; - - if (isNumberInRange(longitude, -180, 180)) { - return longitude; - } - return null; -} - -// NOTE: The following Set utils have been added here, to easily determine where they are used. -// They should be replaced with native Set operations, when they are added to the language. -// Proposal reference: https://github.com/tc39/proposal-set-methods - -export const setUnion = (...sets: Set[]): Set => { - const union = new Set(sets[0]); - for (const set of sets.slice(1)) { - for (const element of set) { - union.add(element); - } - } - return union; -}; - -export const setDifference = (setA: Set, ...sets: Set[]): Set => { - const difference = new Set(setA); - for (const set of sets) { - for (const element of set) { - difference.delete(element); - } - } - return difference; -}; - -export const setIsSuperset = (set: Set, subset: Set): boolean => { - for (const element of subset) { - if (!set.has(element)) { - return false; - } - } - return true; -}; - -export const setIsEqual = (setA: Set, setB: Set): boolean => { - return setA.size === setB.size && setIsSuperset(setA, setB); -}; - -export const handlePromiseError = (promise: Promise, logger: ImmichLogger): void => { - promise.catch((error: Error | any) => logger.error(`Promise error: ${error}`, error?.stack)); -}; - -export enum CacheControl { - PRIVATE_WITH_CACHE = 'private_with_cache', - PRIVATE_WITHOUT_CACHE = 'private_without_cache', - NONE = 'none', -} - -export class ImmichFileResponse { - public readonly path!: string; - public readonly contentType!: string; - public readonly cacheControl!: CacheControl; - - constructor(response: ImmichFileResponse) { - Object.assign(this, response); - } -} - -export interface OpenGraphTags { - title: string; - description: string; - imageUrl?: string; -} - -export function getFileNameWithoutExtension(path: string): string { - return basename(path, extname(path)); -} - -export function getLivePhotoMotionFilename(stillName: string, motionName: string) { - return getFileNameWithoutExtension(stillName) + extname(motionName); -} - -export interface PaginationOptions { - take: number; - skip?: number; -} - -export enum PaginationMode { - LIMIT_OFFSET = 'limit-offset', - SKIP_TAKE = 'skip-take', -} - -export interface PaginatedBuilderOptions { - take: number; - skip?: number; - mode?: PaginationMode; -} - -export interface PaginationResult { - items: T[]; - hasNextPage: boolean; -} - -export type Paginated = Promise>; - -export async function* usePagination( - pageSize: number, - getNextPage: (pagination: PaginationOptions) => PaginationResult | Paginated, -) { - let hasNextPage = true; - - for (let skip = 0; hasNextPage; skip += pageSize) { - const result = await getNextPage({ take: pageSize, skip }); - hasNextPage = result.hasNextPage; - yield result.items; - } -} diff --git a/server/src/utils/bytes.ts b/server/src/utils/bytes.ts new file mode 100644 index 0000000000..e837c81b9e --- /dev/null +++ b/server/src/utils/bytes.ts @@ -0,0 +1,24 @@ +const KiB = Math.pow(1024, 1); +const MiB = Math.pow(1024, 2); +const GiB = Math.pow(1024, 3); +const TiB = Math.pow(1024, 4); +const PiB = Math.pow(1024, 5); + +export const HumanReadableSize = { KiB, MiB, GiB, TiB, PiB }; + +export function asHumanReadable(bytes: number, precision = 1): string { + const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']; + + let magnitude = 0; + let remainder = bytes; + while (remainder >= 1024) { + if (magnitude + 1 < units.length) { + magnitude++; + remainder /= 1024; + } else { + break; + } + } + + return `${remainder.toFixed(magnitude == 0 ? 0 : precision)} ${units[magnitude]}`; +} diff --git a/server/src/infra/infra.utils.ts b/server/src/utils/database.ts similarity index 75% rename from server/src/infra/infra.utils.ts rename to server/src/utils/database.ts index 2b75af3798..3a3e4603b7 100644 --- a/server/src/infra/infra.utils.ts +++ b/server/src/utils/database.ts @@ -1,18 +1,7 @@ import _ from 'lodash'; import { AssetEntity } from 'src/entities/asset.entity'; import { AssetSearchBuilderOptions } from 'src/interfaces/search.repository'; -import { Paginated, PaginatedBuilderOptions, PaginationMode, PaginationOptions, PaginationResult } from 'src/utils'; -import { - Between, - FindManyOptions, - IsNull, - LessThanOrEqual, - MoreThanOrEqual, - Not, - ObjectLiteral, - Repository, - SelectQueryBuilder, -} from 'typeorm'; +import { Between, IsNull, LessThanOrEqual, MoreThanOrEqual, Not, SelectQueryBuilder } from 'typeorm'; /** * Allows optional values unlike the regular Between and uses MoreThanOrEqual @@ -28,47 +17,6 @@ export function OptionalBetween(from?: T, to?: T) { } } -function paginationHelper(items: Entity[], take: number): PaginationResult { - const hasNextPage = items.length > take; - items.splice(take); - - return { items, hasNextPage }; -} - -export async function paginate( - repository: Repository, - { take, skip }: PaginationOptions, - searchOptions?: FindManyOptions, -): Paginated { - const items = await repository.find( - _.omitBy( - { - ...searchOptions, - // Take one more item to check if there's a next page - take: take + 1, - skip, - }, - _.isUndefined, - ), - ); - - return paginationHelper(items, take); -} - -export async function paginatedBuilder( - qb: SelectQueryBuilder, - { take, skip, mode }: PaginatedBuilderOptions, -): Paginated { - if (mode === PaginationMode.LIMIT_OFFSET) { - qb.limit(take + 1).offset(skip); - } else { - qb.take(take + 1).skip(skip); - } - - const items = await qb.getMany(); - return paginationHelper(items, take); -} - export const asVector = (embedding: number[], quote = false) => quote ? `'[${embedding.join(',')}]'` : `[${embedding.join(',')}]`; diff --git a/server/src/utils/file.ts b/server/src/utils/file.ts new file mode 100644 index 0000000000..0a39bdc678 --- /dev/null +++ b/server/src/utils/file.ts @@ -0,0 +1,25 @@ +import { basename, extname } from 'node:path'; + +export function getFileNameWithoutExtension(path: string): string { + return basename(path, extname(path)); +} + +export function getLivePhotoMotionFilename(stillName: string, motionName: string) { + return getFileNameWithoutExtension(stillName) + extname(motionName); +} + +export enum CacheControl { + PRIVATE_WITH_CACHE = 'private_with_cache', + PRIVATE_WITHOUT_CACHE = 'private_without_cache', + NONE = 'none', +} + +export class ImmichFileResponse { + public readonly path!: string; + public readonly contentType!: string; + public readonly cacheControl!: CacheControl; + + constructor(response: ImmichFileResponse) { + Object.assign(this, response); + } +} diff --git a/server/src/infra/instrumentation.ts b/server/src/utils/instrumentation.ts similarity index 98% rename from server/src/infra/instrumentation.ts rename to server/src/utils/instrumentation.ts index a30f0523a6..aa39a7728b 100644 --- a/server/src/infra/instrumentation.ts +++ b/server/src/utils/instrumentation.ts @@ -14,8 +14,8 @@ import { OpenTelemetryModuleOptions } from 'nestjs-otel/lib/interfaces'; import { copyMetadataFromFunctionToFunction } from 'nestjs-otel/lib/opentelemetry.utils'; import { performance } from 'node:perf_hooks'; import { excludePaths } from 'src/config'; +import { serverVersion } from 'src/constants'; import { DecorateAll } from 'src/decorators'; -import { serverVersion } from 'src/domain/domain.constant'; let metricsEnabled = process.env.IMMICH_METRICS === 'true'; const hostMetrics = diff --git a/server/src/infra/logger.ts b/server/src/utils/logger.ts similarity index 100% rename from server/src/infra/logger.ts rename to server/src/utils/logger.ts diff --git a/server/src/domain/media/media.util.ts b/server/src/utils/media.ts similarity index 100% rename from server/src/domain/media/media.util.ts rename to server/src/utils/media.ts diff --git a/server/src/domain/domain.constant.spec.ts b/server/src/utils/mime-types.spec.ts similarity index 68% rename from server/src/domain/domain.constant.spec.ts rename to server/src/utils/mime-types.spec.ts index e8a1c7b728..96c1921ba4 100644 --- a/server/src/domain/domain.constant.spec.ts +++ b/server/src/utils/mime-types.spec.ts @@ -1,4 +1,4 @@ -import { mimeTypes, Version, VersionType } from 'src/domain/domain.constant'; +import { mimeTypes } from 'src/utils/mime-types'; describe('mimeTypes', () => { for (const { mimetype, extension } of [ @@ -196,74 +196,3 @@ describe('mimeTypes', () => { } }); }); - -describe('Version', () => { - const tests = [ - { this: new Version(0, 0, 1), other: new Version(0, 0, 0), compare: 1, type: VersionType.PATCH }, - { this: new Version(0, 1, 0), other: new Version(0, 0, 0), compare: 1, type: VersionType.MINOR }, - { this: new Version(1, 0, 0), other: new Version(0, 0, 0), compare: 1, type: VersionType.MAJOR }, - { this: new Version(0, 0, 0), other: new Version(0, 0, 1), compare: -1, type: VersionType.PATCH }, - { this: new Version(0, 0, 0), other: new Version(0, 1, 0), compare: -1, type: VersionType.MINOR }, - { this: new Version(0, 0, 0), other: new Version(1, 0, 0), compare: -1, type: VersionType.MAJOR }, - { this: new Version(0, 0, 0), other: new Version(0, 0, 0), compare: 0, type: VersionType.EQUAL }, - { this: new Version(0, 0, 1), other: new Version(0, 0, 1), compare: 0, type: VersionType.EQUAL }, - { this: new Version(0, 1, 0), other: new Version(0, 1, 0), compare: 0, type: VersionType.EQUAL }, - { this: new Version(1, 0, 0), other: new Version(1, 0, 0), compare: 0, type: VersionType.EQUAL }, - { this: new Version(1, 0), other: new Version(1, 0, 0), compare: 0, type: VersionType.EQUAL }, - { this: new Version(1, 0), other: new Version(1, 0, 1), compare: -1, type: VersionType.PATCH }, - { this: new Version(1, 1), other: new Version(1, 0, 1), compare: 1, type: VersionType.MINOR }, - { this: new Version(1), other: new Version(1, 0, 0), compare: 0, type: VersionType.EQUAL }, - { this: new Version(1), other: new Version(1, 0, 1), compare: -1, type: VersionType.PATCH }, - ]; - - describe('isOlderThan', () => { - for (const { this: thisVersion, other: otherVersion, compare, type } of tests) { - const expected = compare < 0 ? type : VersionType.EQUAL; - it(`should return '${expected}' when comparing ${thisVersion} to ${otherVersion}`, () => { - expect(thisVersion.isOlderThan(otherVersion)).toEqual(expected); - }); - } - }); - - describe('isEqual', () => { - for (const { this: thisVersion, other: otherVersion, compare } of tests) { - const bool = compare === 0; - it(`should return ${bool} when comparing ${thisVersion} to ${otherVersion}`, () => { - expect(thisVersion.isEqual(otherVersion)).toEqual(bool); - }); - } - }); - - describe('isNewerThan', () => { - for (const { this: thisVersion, other: otherVersion, compare, type } of tests) { - const expected = compare > 0 ? type : VersionType.EQUAL; - it(`should return ${expected} when comparing ${thisVersion} to ${otherVersion}`, () => { - expect(thisVersion.isNewerThan(otherVersion)).toEqual(expected); - }); - } - }); - - describe('fromString', () => { - const tests = [ - { scenario: 'leading v', value: 'v1.72.2', expected: new Version(1, 72, 2) }, - { scenario: 'uppercase v', value: 'V1.72.2', expected: new Version(1, 72, 2) }, - { scenario: 'missing v', value: '1.72.2', expected: new Version(1, 72, 2) }, - { scenario: 'large patch', value: '1.72.123', expected: new Version(1, 72, 123) }, - { scenario: 'large minor', value: '1.123.0', expected: new Version(1, 123, 0) }, - { scenario: 'large major', value: '123.0.0', expected: new Version(123, 0, 0) }, - { scenario: 'major bump', value: 'v2.0.0', expected: new Version(2, 0, 0) }, - { scenario: 'has dash', value: '14.10-1', expected: new Version(14, 10, 1) }, - { scenario: 'missing patch', value: '14.10', expected: new Version(14, 10, 0) }, - { scenario: 'only major', value: '14', expected: new Version(14, 0, 0) }, - ]; - - for (const { scenario, value, expected } of tests) { - it(`should correctly parse ${scenario}`, () => { - const actual = Version.fromString(value); - expect(actual.major).toEqual(expected.major); - expect(actual.minor).toEqual(expected.minor); - expect(actual.patch).toEqual(expected.patch); - }); - } - }); -}); diff --git a/server/src/domain/domain.constant.ts b/server/src/utils/mime-types.ts similarity index 56% rename from server/src/domain/domain.constant.ts rename to server/src/utils/mime-types.ts index 29c4837b6b..789fc6a1c2 100644 --- a/server/src/domain/domain.constant.ts +++ b/server/src/utils/mime-types.ts @@ -1,97 +1,6 @@ -import { Duration } from 'luxon'; -import { readFileSync } from 'node:fs'; -import { extname, join } from 'node:path'; +import { extname } from 'node:path'; import { AssetType } from 'src/entities/asset.entity'; -export const AUDIT_LOG_MAX_DURATION = Duration.fromObject({ days: 100 }); -export const ONE_HOUR = Duration.fromObject({ hours: 1 }); - -export interface IVersion { - major: number; - minor: number; - patch: number; -} - -export enum VersionType { - EQUAL = 0, - PATCH = 1, - MINOR = 2, - MAJOR = 3, -} - -export class Version implements IVersion { - public readonly types = ['major', 'minor', 'patch'] as const; - - constructor( - public major: number, - public minor: number = 0, - public patch: number = 0, - ) {} - - toString() { - return `${this.major}.${this.minor}.${this.patch}`; - } - - toJSON() { - const { major, minor, patch } = this; - return { major, minor, patch }; - } - - static fromString(version: string): Version { - const regex = /v?(?\d+)(?:\.(?\d+))?(?:[.-](?\d+))?/i; - const matchResult = version.match(regex); - if (matchResult) { - const { major, minor = '0', patch = '0' } = matchResult.groups as { [K in keyof IVersion]: string }; - return new Version(Number(major), Number(minor), Number(patch)); - } else { - throw new Error(`Invalid version format: ${version}`); - } - } - - private compare(version: Version): [number, VersionType] { - for (const [i, key] of this.types.entries()) { - const diff = this[key] - version[key]; - if (diff !== 0) { - return [diff > 0 ? 1 : -1, (VersionType.MAJOR - i) as VersionType]; - } - } - - return [0, VersionType.EQUAL]; - } - - isOlderThan(version: Version): VersionType { - const [bool, type] = this.compare(version); - return bool < 0 ? type : VersionType.EQUAL; - } - - isEqual(version: Version): boolean { - const [bool] = this.compare(version); - return bool === 0; - } - - isNewerThan(version: Version): VersionType { - const [bool, type] = this.compare(version); - return bool > 0 ? type : VersionType.EQUAL; - } -} - -export const envName = (process.env.NODE_ENV || 'development').toUpperCase(); -export const isDev = process.env.NODE_ENV === 'development'; - -const { version } = JSON.parse(readFileSync('./package.json', 'utf8')); -export const serverVersion = Version.fromString(version); - -export const APP_MEDIA_LOCATION = process.env.IMMICH_MEDIA_LOCATION || './upload'; -export const WEB_ROOT = process.env.IMMICH_WEB_ROOT || '/usr/src/app/www'; - -const GEODATA_ROOT_PATH = process.env.IMMICH_REVERSE_GEOCODING_ROOT || '/usr/src/resources'; - -export const citiesFile = 'cities500.txt'; -export const geodataDatePath = join(GEODATA_ROOT_PATH, 'geodata-date.txt'); -export const geodataAdmin1Path = join(GEODATA_ROOT_PATH, 'admin1CodesASCII.txt'); -export const geodataAdmin2Path = join(GEODATA_ROOT_PATH, 'admin2Codes.txt'); -export const geodataCities500Path = join(GEODATA_ROOT_PATH, citiesFile); - const image: Record = { '.3fr': ['image/3fr', 'image/x-hasselblad-3fr'], '.ari': ['image/ari', 'image/x-arriflex-ari'], diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts new file mode 100644 index 0000000000..d664a904ea --- /dev/null +++ b/server/src/utils/misc.ts @@ -0,0 +1,32 @@ +import { CLIP_MODEL_INFO } from 'src/constants'; +import { ImmichLogger } from 'src/utils/logger'; + +export const isConnectionAborted = (error: Error | any) => error.code === 'ECONNABORTED'; + +export const handlePromiseError = (promise: Promise, logger: ImmichLogger): void => { + promise.catch((error: Error | any) => logger.error(`Promise error: ${error}`, error?.stack)); +}; + +export interface OpenGraphTags { + title: string; + description: string; + imageUrl?: string; +} + +function cleanModelName(modelName: string): string { + const token = modelName.split('/').at(-1); + if (!token) { + throw new Error(`Invalid model name: ${modelName}`); + } + + return token.replaceAll(':', '_'); +} + +export function getCLIPModelInfo(modelName: string) { + const modelInfo = CLIP_MODEL_INFO[cleanModelName(modelName)]; + if (!modelInfo) { + throw new Error(`Unknown CLIP model: ${modelName}`); + } + + return modelInfo; +} diff --git a/server/src/utils/pagination.ts b/server/src/utils/pagination.ts new file mode 100644 index 0000000000..dec1a9de0c --- /dev/null +++ b/server/src/utils/pagination.ts @@ -0,0 +1,79 @@ +import _ from 'lodash'; +import { FindManyOptions, ObjectLiteral, Repository, SelectQueryBuilder } from 'typeorm'; + +export interface PaginationOptions { + take: number; + skip?: number; +} + +export enum PaginationMode { + LIMIT_OFFSET = 'limit-offset', + SKIP_TAKE = 'skip-take', +} + +export interface PaginatedBuilderOptions { + take: number; + skip?: number; + mode?: PaginationMode; +} + +export interface PaginationResult { + items: T[]; + hasNextPage: boolean; +} + +export type Paginated = Promise>; + +export async function* usePagination( + pageSize: number, + getNextPage: (pagination: PaginationOptions) => PaginationResult | Paginated, +) { + let hasNextPage = true; + + for (let skip = 0; hasNextPage; skip += pageSize) { + const result = await getNextPage({ take: pageSize, skip }); + hasNextPage = result.hasNextPage; + yield result.items; + } +} + +function paginationHelper(items: Entity[], take: number): PaginationResult { + const hasNextPage = items.length > take; + items.splice(take); + + return { items, hasNextPage }; +} + +export async function paginate( + repository: Repository, + { take, skip }: PaginationOptions, + searchOptions?: FindManyOptions, +): Paginated { + const items = await repository.find( + _.omitBy( + { + ...searchOptions, + // Take one more item to check if there's a next page + take: take + 1, + skip, + }, + _.isUndefined, + ), + ); + + return paginationHelper(items, take); +} + +export async function paginatedBuilder( + qb: SelectQueryBuilder, + { take, skip, mode }: PaginatedBuilderOptions, +): Paginated { + if (mode === PaginationMode.LIMIT_OFFSET) { + qb.limit(take + 1).offset(skip); + } else { + qb.take(take + 1).skip(skip); + } + + const items = await qb.getMany(); + return paginationHelper(items, take); +} diff --git a/server/src/utils/set.ts b/server/src/utils/set.ts new file mode 100644 index 0000000000..971d3f7e5d --- /dev/null +++ b/server/src/utils/set.ts @@ -0,0 +1,36 @@ +// NOTE: The following Set utils have been added here, to easily determine where they are used. +// They should be replaced with native Set operations, when they are added to the language. +// Proposal reference: https://github.com/tc39/proposal-set-methods + +export const setUnion = (...sets: Set[]): Set => { + const union = new Set(sets[0]); + for (const set of sets.slice(1)) { + for (const element of set) { + union.add(element); + } + } + return union; +}; + +export const setDifference = (setA: Set, ...sets: Set[]): Set => { + const difference = new Set(setA); + for (const set of sets) { + for (const element of set) { + difference.delete(element); + } + } + return difference; +}; + +export const setIsSuperset = (set: Set, subset: Set): boolean => { + for (const element of subset) { + if (!set.has(element)) { + return false; + } + } + return true; +}; + +export const setIsEqual = (setA: Set, setB: Set): boolean => { + return setA.size === setB.size && setIsSuperset(setA, setB); +}; diff --git a/server/src/infra/sql-generator/index.ts b/server/src/utils/sql.ts similarity index 90% rename from server/src/infra/sql-generator/index.ts rename to server/src/utils/sql.ts index 10730fc36e..f982738064 100644 --- a/server/src/infra/sql-generator/index.ts +++ b/server/src/utils/sql.ts @@ -5,10 +5,10 @@ import { Test } from '@nestjs/testing'; import { TypeOrmModule } from '@nestjs/typeorm'; import { mkdir, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; +import { format } from 'sql-formatter'; +import { databaseConfig } from 'src/database.config'; import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators'; import { databaseEntities } from 'src/entities'; -import { databaseConfig } from 'src/infra/database.config'; -import { SqlLogger } from 'src/infra/sql-generator/sql.logger'; import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; import { AccessRepository } from 'src/repositories/access.repository'; import { AlbumRepository } from 'src/repositories/album.repository'; @@ -26,6 +26,30 @@ import { SystemMetadataRepository } from 'src/repositories/system-metadata.repos import { TagRepository } from 'src/repositories/tag.repository'; import { UserTokenRepository } from 'src/repositories/user-token.repository'; import { UserRepository } from 'src/repositories/user.repository'; +import { Logger } from 'typeorm'; + +export class SqlLogger implements Logger { + queries: string[] = []; + errors: Array<{ error: string | Error; query: string }> = []; + + clear() { + this.queries = []; + this.errors = []; + } + + logQuery(query: string) { + this.queries.push(format(query, { language: 'postgresql' })); + } + + logQueryError(error: string | Error, query: string) { + this.errors.push({ error, query }); + } + + logQuerySlow() {} + logSchemaBuild() {} + logMigration() {} + log() {} +} const reflector = new Reflector(); const repositories = [ @@ -178,7 +202,7 @@ class SqlGenerator { } } -new SqlGenerator({ targetDir: './src/infra/sql' }) +new SqlGenerator({ targetDir: './src/queries' }) .run() .then(() => { console.log('Done'); diff --git a/server/src/utils/version.spec.ts b/server/src/utils/version.spec.ts new file mode 100644 index 0000000000..34c8abb417 --- /dev/null +++ b/server/src/utils/version.spec.ts @@ -0,0 +1,72 @@ +import { Version, VersionType } from 'src/utils/version'; + +describe('Version', () => { + const tests = [ + { this: new Version(0, 0, 1), other: new Version(0, 0, 0), compare: 1, type: VersionType.PATCH }, + { this: new Version(0, 1, 0), other: new Version(0, 0, 0), compare: 1, type: VersionType.MINOR }, + { this: new Version(1, 0, 0), other: new Version(0, 0, 0), compare: 1, type: VersionType.MAJOR }, + { this: new Version(0, 0, 0), other: new Version(0, 0, 1), compare: -1, type: VersionType.PATCH }, + { this: new Version(0, 0, 0), other: new Version(0, 1, 0), compare: -1, type: VersionType.MINOR }, + { this: new Version(0, 0, 0), other: new Version(1, 0, 0), compare: -1, type: VersionType.MAJOR }, + { this: new Version(0, 0, 0), other: new Version(0, 0, 0), compare: 0, type: VersionType.EQUAL }, + { this: new Version(0, 0, 1), other: new Version(0, 0, 1), compare: 0, type: VersionType.EQUAL }, + { this: new Version(0, 1, 0), other: new Version(0, 1, 0), compare: 0, type: VersionType.EQUAL }, + { this: new Version(1, 0, 0), other: new Version(1, 0, 0), compare: 0, type: VersionType.EQUAL }, + { this: new Version(1, 0), other: new Version(1, 0, 0), compare: 0, type: VersionType.EQUAL }, + { this: new Version(1, 0), other: new Version(1, 0, 1), compare: -1, type: VersionType.PATCH }, + { this: new Version(1, 1), other: new Version(1, 0, 1), compare: 1, type: VersionType.MINOR }, + { this: new Version(1), other: new Version(1, 0, 0), compare: 0, type: VersionType.EQUAL }, + { this: new Version(1), other: new Version(1, 0, 1), compare: -1, type: VersionType.PATCH }, + ]; + + describe('isOlderThan', () => { + for (const { this: thisVersion, other: otherVersion, compare, type } of tests) { + const expected = compare < 0 ? type : VersionType.EQUAL; + it(`should return '${expected}' when comparing ${thisVersion} to ${otherVersion}`, () => { + expect(thisVersion.isOlderThan(otherVersion)).toEqual(expected); + }); + } + }); + + describe('isEqual', () => { + for (const { this: thisVersion, other: otherVersion, compare } of tests) { + const bool = compare === 0; + it(`should return ${bool} when comparing ${thisVersion} to ${otherVersion}`, () => { + expect(thisVersion.isEqual(otherVersion)).toEqual(bool); + }); + } + }); + + describe('isNewerThan', () => { + for (const { this: thisVersion, other: otherVersion, compare, type } of tests) { + const expected = compare > 0 ? type : VersionType.EQUAL; + it(`should return ${expected} when comparing ${thisVersion} to ${otherVersion}`, () => { + expect(thisVersion.isNewerThan(otherVersion)).toEqual(expected); + }); + } + }); + + describe('fromString', () => { + const tests = [ + { scenario: 'leading v', value: 'v1.72.2', expected: new Version(1, 72, 2) }, + { scenario: 'uppercase v', value: 'V1.72.2', expected: new Version(1, 72, 2) }, + { scenario: 'missing v', value: '1.72.2', expected: new Version(1, 72, 2) }, + { scenario: 'large patch', value: '1.72.123', expected: new Version(1, 72, 123) }, + { scenario: 'large minor', value: '1.123.0', expected: new Version(1, 123, 0) }, + { scenario: 'large major', value: '123.0.0', expected: new Version(123, 0, 0) }, + { scenario: 'major bump', value: 'v2.0.0', expected: new Version(2, 0, 0) }, + { scenario: 'has dash', value: '14.10-1', expected: new Version(14, 10, 1) }, + { scenario: 'missing patch', value: '14.10', expected: new Version(14, 10, 0) }, + { scenario: 'only major', value: '14', expected: new Version(14, 0, 0) }, + ]; + + for (const { scenario, value, expected } of tests) { + it(`should correctly parse ${scenario}`, () => { + const actual = Version.fromString(value); + expect(actual.major).toEqual(expected.major); + expect(actual.minor).toEqual(expected.minor); + expect(actual.patch).toEqual(expected.patch); + }); + } + }); +}); diff --git a/server/src/utils/version.ts b/server/src/utils/version.ts new file mode 100644 index 0000000000..6eca12eb49 --- /dev/null +++ b/server/src/utils/version.ts @@ -0,0 +1,64 @@ +export type IVersion = { major: number; minor: number; patch: number }; + +export enum VersionType { + EQUAL = 0, + PATCH = 1, + MINOR = 2, + MAJOR = 3, +} + +export class Version implements IVersion { + public readonly types = ['major', 'minor', 'patch'] as const; + + constructor( + public major: number, + public minor: number = 0, + public patch: number = 0, + ) {} + + toString() { + return `${this.major}.${this.minor}.${this.patch}`; + } + + toJSON() { + const { major, minor, patch } = this; + return { major, minor, patch }; + } + + static fromString(version: string): Version { + const regex = /v?(?\d+)(?:\.(?\d+))?(?:[.-](?\d+))?/i; + const matchResult = version.match(regex); + if (matchResult) { + const { major, minor = '0', patch = '0' } = matchResult.groups as { [K in keyof IVersion]: string }; + return new Version(Number(major), Number(minor), Number(patch)); + } else { + throw new Error(`Invalid version format: ${version}`); + } + } + + private compare(version: Version): [number, VersionType] { + for (const [i, key] of this.types.entries()) { + const diff = this[key] - version[key]; + if (diff !== 0) { + return [diff > 0 ? 1 : -1, (VersionType.MAJOR - i) as VersionType]; + } + } + + return [0, VersionType.EQUAL]; + } + + isOlderThan(version: Version): VersionType { + const [bool, type] = this.compare(version); + return bool < 0 ? type : VersionType.EQUAL; + } + + isEqual(version: Version): boolean { + const [bool] = this.compare(version); + return bool === 0; + } + + isNewerThan(version: Version): VersionType { + const [bool, type] = this.compare(version); + return bool > 0 ? type : VersionType.EQUAL; + } +} diff --git a/server/test/fixtures/library.stub.ts b/server/test/fixtures/library.stub.ts index b3fa4b7f02..dde250a7a1 100644 --- a/server/test/fixtures/library.stub.ts +++ b/server/test/fixtures/library.stub.ts @@ -1,6 +1,6 @@ import { join } from 'node:path'; +import { APP_MEDIA_LOCATION } from 'src/constants'; import { THUMBNAIL_DIR } from 'src/cores/storage.core'; -import { APP_MEDIA_LOCATION } from 'src/domain/domain.constant'; import { LibraryEntity, LibraryType } from 'src/entities/library.entity'; import { userStub } from 'test/fixtures/user.stub'; diff --git a/server/test/repositories/database.repository.mock.ts b/server/test/repositories/database.repository.mock.ts index 82abe16a8e..43a2dfb796 100644 --- a/server/test/repositories/database.repository.mock.ts +++ b/server/test/repositories/database.repository.mock.ts @@ -1,5 +1,5 @@ -import { Version } from 'src/domain/domain.constant'; import { IDatabaseRepository } from 'src/interfaces/database.repository'; +import { Version } from 'src/utils/version'; export const newDatabaseRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/utils.ts b/server/test/utils.ts index f44f6fe17d..4feca9946c 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -10,9 +10,8 @@ import { ApiModule } from 'src/apps/api.module'; import { ApiService } from 'src/apps/api.service'; import { AppModule, AppTestModule } from 'src/apps/app.module'; import { MicroservicesService } from 'src/apps/microservices.service'; -import { QueueName } from 'src/domain/job/job.constants'; -import { dataSource } from 'src/infra/database.config'; -import { IJobRepository, JobItem, JobItemHandler } from 'src/interfaces/job.repository'; +import { dataSource } from 'src/database.config'; +import { IJobRepository, JobItem, JobItemHandler, QueueName } from 'src/interfaces/job.repository'; import { IMediaRepository } from 'src/interfaces/media.repository'; import { StorageEventType } from 'src/interfaces/storage.repository'; import { MediaRepository } from 'src/repositories/media.repository';