From d9d00f78e6a68af3a4d28b526cb7e5a84f2a461c Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Tue, 1 Oct 2024 23:38:51 -0400 Subject: [PATCH] WIP --- server/src/services/activity.service.ts | 22 +-- server/src/services/album.service.ts | 21 +-- server/src/services/api-key.service.ts | 26 ++- server/src/services/asset-media.service.ts | 31 +--- server/src/services/asset.service.ts | 28 +--- server/src/services/audit.service.ts | 32 +--- server/src/services/auth.service.ts | 43 ++--- server/src/services/base.service.ts | 86 +++++++++- server/src/services/cli.service.ts | 22 +-- server/src/services/database.service.ts | 16 +- server/src/services/download.service.ts | 19 +-- server/src/services/duplicate.service.ts | 34 +--- server/src/services/job.service.ts | 25 +-- server/src/services/library.service.ts | 57 ++----- server/src/services/map.service.ts | 13 +- server/src/services/media.service.ts | 47 +----- server/src/services/memory.service.ts | 30 ++-- server/src/services/metadata.service.ts | 79 ++------- server/src/services/notification.service.ts | 28 +--- server/src/services/partner.service.ts | 25 ++- server/src/services/person.service.ts | 122 +++++--------- server/src/services/search.service.ts | 31 +--- server/src/services/server.service.ts | 29 +--- server/src/services/session.service.ts | 16 +- server/src/services/shared-link.service.ts | 38 ++--- server/src/services/smart-info.service.ts | 38 +---- server/src/services/stack.service.ts | 14 +- .../services/storage-template.service.spec.ts | 47 +----- .../src/services/storage-template.service.ts | 42 +---- server/src/services/storage.service.spec.ts | 21 +-- server/src/services/storage.service.ts | 25 +-- server/src/services/sync.service.ts | 15 +- server/src/services/system-config.service.ts | 17 +- .../src/services/system-metadata.service.ts | 14 +- server/src/services/tag.service.ts | 39 ++--- server/src/services/timeline.service.ts | 20 +-- server/src/services/trash.service.ts | 20 +-- server/src/services/user-admin.service.ts | 28 +--- server/src/services/user.service.ts | 32 +--- server/src/services/version.service.spec.ts | 53 ++---- server/src/services/version.service.ts | 30 +--- server/src/services/view.service.spec.ts | 7 +- server/src/services/view.service.ts | 7 +- server/test/utils.ts | 158 ++++++++++++++++++ 44 files changed, 534 insertions(+), 1013 deletions(-) create mode 100644 server/test/utils.ts diff --git a/server/src/services/activity.service.ts b/server/src/services/activity.service.ts index 1e4034de936fad..0309fcb9362e5a 100644 --- a/server/src/services/activity.service.ts +++ b/server/src/services/activity.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { ActivityCreateDto, ActivityDto, @@ -13,20 +13,14 @@ import { import { AuthDto } from 'src/dtos/auth.dto'; import { ActivityEntity } from 'src/entities/activity.entity'; import { Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IActivityRepository } from 'src/interfaces/activity.interface'; +import { BaseService } from 'src/services/base.service'; import { requireAccess } from 'src/utils/access'; @Injectable() -export class ActivityService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IActivityRepository) private repository: IActivityRepository, - ) {} - +export class ActivityService extends BaseService { async getAll(auth: AuthDto, dto: ActivitySearchDto): Promise { await requireAccess(this.access, { auth, permission: Permission.ALBUM_READ, ids: [dto.albumId] }); - const activities = await this.repository.search({ + const activities = await this.activityRepository.search({ userId: dto.userId, albumId: dto.albumId, assetId: dto.level === ReactionLevel.ALBUM ? null : dto.assetId, @@ -38,7 +32,7 @@ export class ActivityService { async getStatistics(auth: AuthDto, dto: ActivityDto): Promise { await requireAccess(this.access, { auth, permission: Permission.ALBUM_READ, ids: [dto.albumId] }); - return { comments: await this.repository.getStatistics(dto.assetId, dto.albumId) }; + return { comments: await this.activityRepository.getStatistics(dto.assetId, dto.albumId) }; } async create(auth: AuthDto, dto: ActivityCreateDto): Promise> { @@ -55,7 +49,7 @@ export class ActivityService { if (dto.type === ReactionType.LIKE) { delete dto.comment; - [activity] = await this.repository.search({ + [activity] = await this.activityRepository.search({ ...common, // `null` will search for an album like assetId: dto.assetId ?? null, @@ -65,7 +59,7 @@ export class ActivityService { } if (!activity) { - activity = await this.repository.create({ + activity = await this.activityRepository.create({ ...common, isLiked: dto.type === ReactionType.LIKE, comment: dto.comment, @@ -77,6 +71,6 @@ export class ActivityService { async delete(auth: AuthDto, id: string): Promise { await requireAccess(this.access, { auth, permission: Permission.ACTIVITY_DELETE, ids: [id] }); - await this.repository.delete(id); + await this.activityRepository.delete(id); } } diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index 2f5d2308415ff9..5ecfad3b49127e 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { AddUsersDto, AlbumInfoDto, @@ -17,26 +17,13 @@ import { AlbumUserEntity } from 'src/entities/album-user.entity'; import { AlbumEntity } from 'src/entities/album.entity'; import { AssetEntity } from 'src/entities/asset.entity'; import { Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IAlbumUserRepository } from 'src/interfaces/album-user.interface'; -import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IEventRepository } from 'src/interfaces/event.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; +import { AlbumAssetCount, AlbumInfoOptions } from 'src/interfaces/album.interface'; +import { BaseService } from 'src/services/base.service'; import { checkAccess, requireAccess } from 'src/utils/access'; import { addAssets, removeAssets } from 'src/utils/asset.util'; @Injectable() -export class AlbumService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IAlbumRepository) private albumRepository: IAlbumRepository, - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(IUserRepository) private userRepository: IUserRepository, - @Inject(IAlbumUserRepository) private albumUserRepository: IAlbumUserRepository, - ) {} - +export class AlbumService extends BaseService { async getStatistics(auth: AuthDto): Promise { const [owned, shared, notShared] = await Promise.all([ this.albumRepository.getOwned(auth.user.id), diff --git a/server/src/services/api-key.service.ts b/server/src/services/api-key.service.ts index 7dd1ed5c268ba7..1364280e4ae5c9 100644 --- a/server/src/services/api-key.service.ts +++ b/server/src/services/api-key.service.ts @@ -1,18 +1,12 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto, APIKeyUpdateDto } from 'src/dtos/api-key.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { APIKeyEntity } from 'src/entities/api-key.entity'; -import { IKeyRepository } from 'src/interfaces/api-key.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; +import { BaseService } from 'src/services/base.service'; import { isGranted } from 'src/utils/access'; @Injectable() -export class APIKeyService { - constructor( - @Inject(ICryptoRepository) private crypto: ICryptoRepository, - @Inject(IKeyRepository) private repository: IKeyRepository, - ) {} - +export class APIKeyService extends BaseService { async create(auth: AuthDto, dto: APIKeyCreateDto): Promise { const secret = this.crypto.newPassword(32); @@ -20,7 +14,7 @@ export class APIKeyService { throw new BadRequestException('Cannot grant permissions you do not have'); } - const entity = await this.repository.create({ + const entity = await this.keyRepository.create({ key: this.crypto.hashSha256(secret), name: dto.name || 'API Key', userId: auth.user.id, @@ -31,27 +25,27 @@ export class APIKeyService { } async update(auth: AuthDto, id: string, dto: APIKeyUpdateDto): Promise { - const exists = await this.repository.getById(auth.user.id, id); + const exists = await this.keyRepository.getById(auth.user.id, id); if (!exists) { throw new BadRequestException('API Key not found'); } - const key = await this.repository.update(auth.user.id, id, { name: dto.name }); + const key = await this.keyRepository.update(auth.user.id, id, { name: dto.name }); return this.map(key); } async delete(auth: AuthDto, id: string): Promise { - const exists = await this.repository.getById(auth.user.id, id); + const exists = await this.keyRepository.getById(auth.user.id, id); if (!exists) { throw new BadRequestException('API Key not found'); } - await this.repository.delete(auth.user.id, id); + await this.keyRepository.delete(auth.user.id, id); } async getById(auth: AuthDto, id: string): Promise { - const key = await this.repository.getById(auth.user.id, id); + const key = await this.keyRepository.getById(auth.user.id, id); if (!key) { throw new BadRequestException('API Key not found'); } @@ -59,7 +53,7 @@ export class APIKeyService { } async getAll(auth: AuthDto): Promise { - const keys = await this.repository.getByUserId(auth.user.id); + const keys = await this.keyRepository.getByUserId(auth.user.id); return keys.map((key) => this.map(key)); } diff --git a/server/src/services/asset-media.service.ts b/server/src/services/asset-media.service.ts index e1b30e891f9360..6bc058803391ef 100644 --- a/server/src/services/asset-media.service.ts +++ b/server/src/services/asset-media.service.ts @@ -1,10 +1,4 @@ -import { - BadRequestException, - Inject, - Injectable, - InternalServerErrorException, - NotFoundException, -} from '@nestjs/common'; +import { BadRequestException, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; import { extname } from 'node:path'; import sanitize from 'sanitize-filename'; import { StorageCore } from 'src/cores/storage.core'; @@ -28,13 +22,8 @@ import { import { AuthDto } from 'src/dtos/auth.dto'; import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity } from 'src/entities/asset.entity'; import { AssetStatus, AssetType, CacheControl, Permission, StorageFolder } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IEventRepository } from 'src/interfaces/event.interface'; -import { IJobRepository, JobName } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; +import { JobName } from 'src/interfaces/job.interface'; +import { BaseService } from 'src/services/base.service'; import { requireAccess, requireUploadAccess } from 'src/utils/access'; import { getAssetFiles, onBeforeLink } from 'src/utils/asset.util'; import { ImmichFileResponse } from 'src/utils/file'; @@ -56,19 +45,7 @@ export interface UploadFile { } @Injectable() -export class AssetMediaService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(IStorageRepository) private storageRepository: IStorageRepository, - @Inject(IUserRepository) private userRepository: IUserRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, - ) { - this.logger.setContext(AssetMediaService.name); - } - +export class AssetMediaService extends BaseService { async getUploadAssetIdByChecksum(auth: AuthDto, checksum?: string): Promise { if (!checksum) { return; diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index 11416880280c68..80e884f3f9fd15 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject } from '@nestjs/common'; +import { BadRequestException } from '@nestjs/common'; import _ from 'lodash'; import { DateTime, Duration } from 'luxon'; import { @@ -20,46 +20,20 @@ import { AuthDto } from 'src/dtos/auth.dto'; import { MemoryLaneDto } from 'src/dtos/search.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { AssetStatus, Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { IEventRepository } from 'src/interfaces/event.interface'; import { IAssetDeleteJob, - IJobRepository, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobItem, JobName, JobStatus, } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IPartnerRepository } from 'src/interfaces/partner.interface'; -import { IStackRepository } from 'src/interfaces/stack.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; import { BaseService } from 'src/services/base.service'; import { requireAccess } from 'src/utils/access'; import { getAssetFiles, getMyPartnerIds, onAfterUnlink, onBeforeLink, onBeforeUnlink } from 'src/utils/asset.util'; import { usePagination } from 'src/utils/pagination'; export class AssetService extends BaseService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(IUserRepository) private userRepository: IUserRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository, - @Inject(IStackRepository) private stackRepository: IStackRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(AssetService.name); - } - async getMemoryLane(auth: AuthDto, dto: MemoryLaneDto): Promise { const partnerIds = await getMyPartnerIds({ userId: auth.user.id, diff --git a/server/src/services/audit.service.ts b/server/src/services/audit.service.ts index ced0f49c63716d..66d3035af7e5d8 100644 --- a/server/src/services/audit.service.ts +++ b/server/src/services/audit.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; import { resolve } from 'node:path'; import { AUDIT_LOG_MAX_DURATION } from 'src/constants'; @@ -21,36 +21,16 @@ import { StorageFolder, UserPathType, } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IAuditRepository } from 'src/interfaces/audit.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IPersonRepository } from 'src/interfaces/person.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; +import { BaseService } from 'src/services/base.service'; import { requireAccess } from 'src/utils/access'; import { getAssetFiles } from 'src/utils/asset.util'; import { usePagination } from 'src/utils/pagination'; @Injectable() -export class AuditService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, - @Inject(IPersonRepository) private personRepository: IPersonRepository, - @Inject(IAuditRepository) private repository: IAuditRepository, - @Inject(IStorageRepository) private storageRepository: IStorageRepository, - @Inject(IUserRepository) private userRepository: IUserRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, - ) { - this.logger.setContext(AuditService.name); - } - +export class AuditService extends BaseService { async handleCleanup(): Promise { - await this.repository.removeBefore(DateTime.now().minus(AUDIT_LOG_MAX_DURATION).toJSDate()); + await this.auditRepository.removeBefore(DateTime.now().minus(AUDIT_LOG_MAX_DURATION).toJSDate()); return JobStatus.SUCCESS; } @@ -58,7 +38,7 @@ export class AuditService { const userId = dto.userId || auth.user.id; await requireAccess(this.access, { auth, permission: Permission.TIMELINE_READ, ids: [userId] }); - const audits = await this.repository.getAfter(dto.after, { + const audits = await this.auditRepository.getAfter(dto.after, { userIds: [userId], entityType: dto.entityType, action: DatabaseAction.DELETE, @@ -81,7 +61,7 @@ export class AuditService { ); } - const checksum = await this.cryptoRepository.hashFile(filename); + const checksum = await this.crypto.hashFile(filename); results.push({ filename, checksum: checksum.toString('base64') }); } return results; diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index 3e4a55b7ff044b..839704a1ed65e7 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -1,7 +1,6 @@ import { BadRequestException, ForbiddenException, - Inject, Injectable, InternalServerErrorException, UnauthorizedException, @@ -13,6 +12,7 @@ import { IncomingHttpHeaders } from 'node:http'; import { Issuer, UserinfoResponse, custom, generators } from 'openid-client'; import { SystemConfig } from 'src/config'; import { LOGIN_URL, MOBILE_REDIRECT, SALT_ROUNDS } from 'src/constants'; +import { OnEvent } from 'src/decorators'; import { AuthDto, ChangePasswordDto, @@ -30,15 +30,6 @@ import { import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto'; import { UserEntity } from 'src/entities/user.entity'; import { AuthType, Permission } from 'src/enum'; -import { IKeyRepository } from 'src/interfaces/api-key.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { IEventRepository } from 'src/interfaces/event.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { ISessionRepository } from 'src/interfaces/session.interface'; -import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; import { BaseService } from 'src/services/base.service'; import { isGranted } from 'src/utils/access'; import { HumanReadableSize } from 'src/utils/bytes'; @@ -72,20 +63,8 @@ export type ValidateRequest = { @Injectable() export class AuthService extends BaseService { - constructor( - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - @Inject(IUserRepository) private userRepository: IUserRepository, - @Inject(ISessionRepository) private sessionRepository: ISessionRepository, - @Inject(ISharedLinkRepository) private sharedLinkRepository: ISharedLinkRepository, - @Inject(IKeyRepository) private keyRepository: IKeyRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(AuthService.name); - + @OnEvent({ name: 'app.bootstrap' }) + onBootstrap() { custom.setHttpOptionsDefaults({ timeout: 30_000 }); } @@ -135,7 +114,7 @@ export class AuthService extends BaseService { throw new BadRequestException('Wrong password'); } - const hashedPassword = await this.cryptoRepository.hashBcrypt(newPassword, SALT_ROUNDS); + const hashedPassword = await this.crypto.hashBcrypt(newPassword, SALT_ROUNDS); const updatedUser = await this.userRepository.update(user.id, { password: hashedPassword }); @@ -149,7 +128,7 @@ export class AuthService extends BaseService { } const admin = await createUser( - { userRepo: this.userRepository, cryptoRepo: this.cryptoRepository }, + { userRepo: this.userRepository, cryptoRepo: this.crypto }, { isAdmin: true, email: dto.email, @@ -273,7 +252,7 @@ export class AuthService extends BaseService { const userName = profile.name ?? `${profile.given_name || ''} ${profile.family_name || ''}`; user = await createUser( - { userRepo: this.userRepository, cryptoRepo: this.cryptoRepository }, + { userRepo: this.userRepository, cryptoRepo: this.crypto }, { name: userName, email: profile.email, @@ -400,7 +379,7 @@ export class AuthService extends BaseService { } private async validateApiKey(key: string): Promise { - const hashedKey = this.cryptoRepository.hashSha256(key); + const hashedKey = this.crypto.hashSha256(key); const apiKey = await this.keyRepository.getKey(hashedKey); if (apiKey?.user) { return { user: apiKey.user, apiKey }; @@ -413,11 +392,11 @@ export class AuthService extends BaseService { if (!user || !user.password) { return false; } - return this.cryptoRepository.compareBcrypt(inputPassword, user.password); + return this.crypto.compareBcrypt(inputPassword, user.password); } private async validateSession(tokenValue: string): Promise { - const hashedToken = this.cryptoRepository.hashSha256(tokenValue); + const hashedToken = this.crypto.hashSha256(tokenValue); const session = await this.sessionRepository.getByToken(hashedToken); if (session?.user) { @@ -435,8 +414,8 @@ export class AuthService extends BaseService { } private async createLoginResponse(user: UserEntity, loginDetails: LoginDetails) { - const key = this.cryptoRepository.newPassword(32); - const token = this.cryptoRepository.hashSha256(key); + const key = this.crypto.newPassword(32); + const token = this.crypto.hashSha256(key); await this.sessionRepository.create({ token, diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index a2ddcb1e5000a0..7d4e4862044cc0 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -1,16 +1,98 @@ import { Inject } from '@nestjs/common'; import { SystemConfig } from 'src/config'; +import { StorageCore } from 'src/cores/storage.core'; +import { IAccessRepository } from 'src/interfaces/access.interface'; +import { IActivityRepository } from 'src/interfaces/activity.interface'; +import { IAlbumUserRepository } from 'src/interfaces/album-user.interface'; +import { IAlbumRepository } from 'src/interfaces/album.interface'; +import { IKeyRepository } from 'src/interfaces/api-key.interface'; +import { IAssetRepository } from 'src/interfaces/asset.interface'; +import { IAuditRepository } from 'src/interfaces/audit.interface'; import { IConfigRepository } from 'src/interfaces/config.interface'; +import { ICryptoRepository } from 'src/interfaces/crypto.interface'; +import { IDatabaseRepository } from 'src/interfaces/database.interface'; +import { IEventRepository } from 'src/interfaces/event.interface'; +import { IJobRepository } from 'src/interfaces/job.interface'; +import { ILibraryRepository } from 'src/interfaces/library.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; +import { IMapRepository } from 'src/interfaces/map.interface'; +import { IMediaRepository } from 'src/interfaces/media.interface'; +import { IMemoryRepository } from 'src/interfaces/memory.interface'; +import { IMetadataRepository } from 'src/interfaces/metadata.interface'; +import { IMetricRepository } from 'src/interfaces/metric.interface'; +import { IMoveRepository } from 'src/interfaces/move.interface'; +import { INotificationRepository } from 'src/interfaces/notification.interface'; +import { IPartnerRepository } from 'src/interfaces/partner.interface'; +import { IPersonRepository } from 'src/interfaces/person.interface'; +import { ISearchRepository } from 'src/interfaces/search.interface'; +import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; +import { ISessionRepository } from 'src/interfaces/session.interface'; +import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; +import { IStackRepository } from 'src/interfaces/stack.interface'; +import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { ITagRepository } from 'src/interfaces/tag.interface'; +import { ITrashRepository } from 'src/interfaces/trash.interface'; +import { IUserRepository } from 'src/interfaces/user.interface'; +import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface'; +import { IViewRepository } from 'src/interfaces/view.interface'; import { getConfig, updateConfig } from 'src/utils/config'; export class BaseService { + protected storageCore: StorageCore; + constructor( + @Inject(IAccessRepository) protected access: IAccessRepository, + @Inject(ILoggerRepository) protected logger: ILoggerRepository, + @Inject(ICryptoRepository) protected crypto: ICryptoRepository, + + @Inject(IActivityRepository) protected activityRepository: IActivityRepository, + @Inject(IAuditRepository) protected auditRepository: IAuditRepository, + @Inject(IAlbumRepository) protected albumRepository: IAlbumRepository, + @Inject(IAlbumUserRepository) protected albumUserRepository: IAlbumUserRepository, + @Inject(IAssetRepository) protected assetRepository: IAssetRepository, @Inject(IConfigRepository) protected configRepository: IConfigRepository, + @Inject(IDatabaseRepository) protected databaseRepository: IDatabaseRepository, + @Inject(IEventRepository) protected eventRepository: IEventRepository, + @Inject(IJobRepository) protected jobRepository: IJobRepository, + @Inject(IKeyRepository) protected keyRepository: IKeyRepository, + @Inject(ILibraryRepository) protected libraryRepository: ILibraryRepository, + @Inject(IMachineLearningRepository) protected machineLearningRepository: IMachineLearningRepository, + @Inject(IMapRepository) protected mapRepository: IMapRepository, + @Inject(IMediaRepository) protected mediaRepository: IMediaRepository, + @Inject(IMemoryRepository) protected memoryRepository: IMemoryRepository, + @Inject(IMetadataRepository) protected metadataRepository: IMetadataRepository, + @Inject(IMetricRepository) protected metricRepository: IMetricRepository, + @Inject(IMoveRepository) protected moveRepository: IMoveRepository, + @Inject(INotificationRepository) protected notificationRepository: INotificationRepository, + @Inject(IPartnerRepository) protected partnerRepository: IPartnerRepository, + @Inject(IPersonRepository) protected personRepository: IPersonRepository, + @Inject(ISearchRepository) protected searchRepository: ISearchRepository, + @Inject(IServerInfoRepository) protected serverInfoRepository: IServerInfoRepository, + @Inject(ISessionRepository) protected sessionRepository: ISessionRepository, + @Inject(ISharedLinkRepository) protected sharedLinkRepository: ISharedLinkRepository, + @Inject(IStackRepository) protected stackRepository: IStackRepository, + @Inject(IStorageRepository) protected storageRepository: IStorageRepository, @Inject(ISystemMetadataRepository) protected systemMetadataRepository: ISystemMetadataRepository, - @Inject(ILoggerRepository) protected logger: ILoggerRepository, - ) {} + @Inject(ITagRepository) protected tagRepository: ITagRepository, + @Inject(ITrashRepository) protected trashRepository: ITrashRepository, + @Inject(IUserRepository) protected userRepository: IUserRepository, + @Inject(IVersionHistoryRepository) protected versionRepository: IVersionHistoryRepository, + @Inject(IViewRepository) protected viewRepository: IViewRepository, + ) { + this.logger.setContext(this.constructor.name); + this.storageCore = StorageCore.create( + assetRepository, + configRepository, + crypto, + moveRepository, + personRepository, + storageRepository, + systemMetadataRepository, + this.logger, + ); + } private get repos() { return { diff --git a/server/src/services/cli.service.ts b/server/src/services/cli.service.ts index 5abd1fab2906b2..acc9c9c80b4254 100644 --- a/server/src/services/cli.service.ts +++ b/server/src/services/cli.service.ts @@ -1,26 +1,10 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { SALT_ROUNDS } from 'src/constants'; import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; import { BaseService } from 'src/services/base.service'; @Injectable() export class CliService extends BaseService { - constructor( - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(IUserRepository) private userRepository: IUserRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(CliService.name); - } - async listUsers(): Promise { const users = await this.userRepository.getList({ withDeleted: true }); return users.map((user) => mapUserAdmin(user)); @@ -33,8 +17,8 @@ export class CliService extends BaseService { } const providedPassword = await ask(mapUserAdmin(admin)); - const password = providedPassword || this.cryptoRepository.newPassword(24); - const hashedPassword = await this.cryptoRepository.hashBcrypt(password, SALT_ROUNDS); + const password = providedPassword || this.crypto.newPassword(24); + const hashedPassword = await this.crypto.hashBcrypt(password, SALT_ROUNDS); await this.userRepository.update(admin.id, { password: hashedPassword }); diff --git a/server/src/services/database.service.ts b/server/src/services/database.service.ts index 9ba190d30afaec..363266c6aef6af 100644 --- a/server/src/services/database.service.ts +++ b/server/src/services/database.service.ts @@ -1,17 +1,15 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { Duration } from 'luxon'; import semver from 'semver'; import { OnEvent } from 'src/decorators'; -import { IConfigRepository } from 'src/interfaces/config.interface'; import { DatabaseExtension, DatabaseLock, EXTENSION_NAMES, - IDatabaseRepository, VectorExtension, VectorIndex, } from 'src/interfaces/database.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { BaseService } from 'src/services/base.service'; type CreateFailedArgs = { name: string; extension: string; otherName: string }; type UpdateFailedArgs = { name: string; extension: string; availableVersion: string }; @@ -63,17 +61,9 @@ const messages = { const RETRY_DURATION = Duration.fromObject({ seconds: 5 }); @Injectable() -export class DatabaseService { +export class DatabaseService extends BaseService { private reconnection?: NodeJS.Timeout; - constructor( - @Inject(IConfigRepository) private configRepository: IConfigRepository, - @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, - ) { - this.logger.setContext(DatabaseService.name); - } - @OnEvent({ name: 'app.bootstrap', priority: -200 }) async onBootstrap() { const version = await this.databaseRepository.getPostgresVersion(); diff --git a/server/src/services/download.service.ts b/server/src/services/download.service.ts index 988b859ff882f0..172ec9737a735b 100644 --- a/server/src/services/download.service.ts +++ b/server/src/services/download.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { parse } from 'node:path'; import { StorageCore } from 'src/cores/storage.core'; import { AssetIdsDto } from 'src/dtos/asset.dto'; @@ -6,26 +6,15 @@ import { AuthDto } from 'src/dtos/auth.dto'; import { DownloadArchiveInfo, DownloadInfoDto, DownloadResponseDto } from 'src/dtos/download.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { ImmichReadStream, IStorageRepository } from 'src/interfaces/storage.interface'; +import { ImmichReadStream } from 'src/interfaces/storage.interface'; +import { BaseService } from 'src/services/base.service'; import { requireAccess } from 'src/utils/access'; import { HumanReadableSize } from 'src/utils/bytes'; import { usePagination } from 'src/utils/pagination'; import { getPreferences } from 'src/utils/preferences'; @Injectable() -export class DownloadService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, - @Inject(IStorageRepository) private storageRepository: IStorageRepository, - ) { - this.logger.setContext(DownloadService.name); - } - +export class DownloadService extends BaseService { async getDownloadInfo(auth: AuthDto, dto: DownloadInfoDto): Promise { const targetSize = dto.archiveSize || HumanReadableSize.GiB * 4; const archives: DownloadArchiveInfo[] = []; diff --git a/server/src/services/duplicate.service.ts b/server/src/services/duplicate.service.ts index f5baa611ff0446..7417619da99814 100644 --- a/server/src/services/duplicate.service.ts +++ b/server/src/services/duplicate.service.ts @@ -1,22 +1,11 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { DuplicateResponseDto, mapDuplicateResponse } from 'src/dtos/duplicate.dto'; import { AssetEntity } from 'src/entities/asset.entity'; -import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { - IBaseJob, - IEntityJob, - IJobRepository, - JOBS_ASSET_PAGINATION_SIZE, - JobName, - JobStatus, -} from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { AssetDuplicateResult, ISearchRepository } from 'src/interfaces/search.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { WithoutProperty } from 'src/interfaces/asset.interface'; +import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, JobStatus } from 'src/interfaces/job.interface'; +import { AssetDuplicateResult } from 'src/interfaces/search.interface'; import { BaseService } from 'src/services/base.service'; import { getAssetFiles } from 'src/utils/asset.util'; import { isDuplicateDetectionEnabled } from 'src/utils/misc'; @@ -24,19 +13,6 @@ import { usePagination } from 'src/utils/pagination'; @Injectable() export class DuplicateService extends BaseService { - constructor( - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(ISearchRepository) private searchRepository: ISearchRepository, - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(DuplicateService.name); - } - async getDuplicates(auth: AuthDto): Promise { const res = await this.assetRepository.getDuplicates({ userIds: [auth.user.id] }); @@ -126,7 +102,7 @@ export class DuplicateService extends BaseService { ), ]; - const targetDuplicateId = asset.duplicateId ?? duplicateIds.shift() ?? this.cryptoRepository.randomUUID(); + const targetDuplicateId = asset.duplicateId ?? duplicateIds.shift() ?? this.crypto.randomUUID(); const assetIdsToUpdate = duplicateAssets .filter((asset) => asset.duplicateId !== targetDuplicateId) .map((duplicate) => duplicate.assetId); diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index 7ff76447968bc1..e18bad28d6ab08 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -1,15 +1,12 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { snakeCase } from 'lodash'; import { OnEvent } from 'src/decorators'; import { mapAsset } from 'src/dtos/asset-response.dto'; import { AllJobStatusResponseDto, JobCommandDto, JobCreateDto, JobStatusDto } from 'src/dtos/job.dto'; import { AssetType, ManualJobName } from 'src/enum'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ArgOf, IEventRepository } from 'src/interfaces/event.interface'; +import { ArgOf } from 'src/interfaces/event.interface'; import { ConcurrentQueueName, - IJobRepository, JobCommand, JobHandler, JobItem, @@ -18,10 +15,6 @@ import { QueueCleanType, QueueName, } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IMetricRepository } from 'src/interfaces/metric.interface'; -import { IPersonRepository } from 'src/interfaces/person.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { BaseService } from 'src/services/base.service'; const asJobItem = (dto: JobCreateDto): JobItem => { @@ -48,20 +41,6 @@ const asJobItem = (dto: JobCreateDto): JobItem => { export class JobService extends BaseService { private isMicroservices = false; - constructor( - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(IPersonRepository) private personRepository: IPersonRepository, - @Inject(IMetricRepository) private metricRepository: IMetricRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(JobService.name); - } - @OnEvent({ name: 'app.bootstrap' }) onBootstrap(app: ArgOf<'app.bootstrap'>) { this.isMicroservices = app === 'microservices'; diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts index abffad81661336..4ffe0ddb261946 100644 --- a/server/src/services/library.service.ts +++ b/server/src/services/library.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { R_OK } from 'node:constants'; import path, { basename, parse } from 'node:path'; import picomatch from 'picomatch'; @@ -17,24 +17,16 @@ import { import { AssetEntity } from 'src/entities/asset.entity'; import { LibraryEntity } from 'src/entities/library.entity'; import { AssetType } from 'src/enum'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface'; +import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; import { IEntityJob, - IJobRepository, ILibraryAssetJob, ILibraryFileJob, JobName, JOBS_LIBRARY_PAGINATION_SIZE, JobStatus, } from 'src/interfaces/job.interface'; -import { ILibraryRepository } from 'src/interfaces/library.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { BaseService } from 'src/services/base.service'; import { mimeTypes } from 'src/utils/mime-types'; import { handlePromiseError } from 'src/utils/misc'; @@ -47,21 +39,6 @@ export class LibraryService extends BaseService { private watchLock = false; private watchers: Record Promise> = {}; - constructor( - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, - @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(ILibraryRepository) private repository: ILibraryRepository, - @Inject(IStorageRepository) private storageRepository: IStorageRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(LibraryService.name); - } - @OnEvent({ name: 'app.bootstrap' }) async onBootstrap() { const config = await this.getConfig({ withCache: false }); @@ -217,14 +194,14 @@ export class LibraryService extends BaseService { return false; } - const libraries = await this.repository.getAll(false); + const libraries = await this.libraryRepository.getAll(false); for (const library of libraries) { await this.watch(library.id); } } async getStatistics(id: string): Promise { - const statistics = await this.repository.getStatistics(id); + const statistics = await this.libraryRepository.getStatistics(id); if (!statistics) { throw new BadRequestException(`Library ${id} not found`); } @@ -237,13 +214,13 @@ export class LibraryService extends BaseService { } async getAll(): Promise { - const libraries = await this.repository.getAll(false); + const libraries = await this.libraryRepository.getAll(false); return libraries.map((library) => mapLibrary(library)); } async handleQueueCleanup(): Promise { this.logger.debug('Cleaning up any pending library deletions'); - const pendingDeletion = await this.repository.getAllDeleted(); + const pendingDeletion = await this.libraryRepository.getAllDeleted(); await this.jobRepository.queueAll( pendingDeletion.map((libraryToDelete) => ({ name: JobName.LIBRARY_DELETE, data: { id: libraryToDelete.id } })), ); @@ -251,7 +228,7 @@ export class LibraryService extends BaseService { } async create(dto: CreateLibraryDto): Promise { - const library = await this.repository.create({ + const library = await this.libraryRepository.create({ ownerId: dto.ownerId, name: dto.name ?? 'New External Library', importPaths: dto.importPaths ?? [], @@ -326,7 +303,7 @@ export class LibraryService extends BaseService { async update(id: string, dto: UpdateLibraryDto): Promise { await this.findOrFail(id); - const library = await this.repository.update({ id, ...dto }); + const library = await this.libraryRepository.update({ id, ...dto }); if (dto.importPaths) { const validation = await this.validate(id, { importPaths: dto.importPaths }); @@ -349,7 +326,7 @@ export class LibraryService extends BaseService { await this.unwatch(id); } - await this.repository.softDelete(id); + await this.libraryRepository.softDelete(id); await this.jobRepository.queue({ name: JobName.LIBRARY_DELETE, data: { id } }); } @@ -379,7 +356,7 @@ export class LibraryService extends BaseService { if (!assetsFound) { this.logger.log(`Deleting library ${libraryId}`); - await this.repository.delete(libraryId); + await this.libraryRepository.delete(libraryId); } return JobStatus.SUCCESS; } @@ -407,7 +384,7 @@ export class LibraryService extends BaseService { this.logger.log(`Importing new library asset: ${assetPath}`); - const library = await this.repository.get(job.id, true); + const library = await this.libraryRepository.get(job.id, true); if (!library || library.deletedAt) { this.logger.error('Cannot import asset into deleted library'); return JobStatus.FAILED; @@ -416,7 +393,7 @@ export class LibraryService extends BaseService { // TODO: device asset id is deprecated, remove it const deviceAssetId = `${basename(assetPath)}`.replaceAll(/\s+/g, ''); - const pathHash = this.cryptoRepository.hashSha1(`path:${assetPath}`); + const pathHash = this.crypto.hashSha1(`path:${assetPath}`); // TODO: doesn't xmp replace the file extension? Will need investigation let sidecarPath: string | null = null; @@ -477,7 +454,7 @@ export class LibraryService extends BaseService { await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_CLEANUP, data: {} }); - const libraries = await this.repository.getAll(true); + const libraries = await this.libraryRepository.getAll(true); await this.jobRepository.queueAll( libraries.map((library) => ({ name: JobName.LIBRARY_QUEUE_SYNC_FILES, @@ -553,7 +530,7 @@ export class LibraryService extends BaseService { } async handleQueueSyncFiles(job: IEntityJob): Promise { - const library = await this.repository.get(job.id); + const library = await this.libraryRepository.get(job.id); if (!library) { this.logger.debug(`Library ${job.id} not found, skipping refresh`); return JobStatus.SKIPPED; @@ -598,13 +575,13 @@ export class LibraryService extends BaseService { this.logger.warn(`No valid import paths found for library ${library.id}`); } - await this.repository.update({ id: job.id, refreshedAt: new Date() }); + await this.libraryRepository.update({ id: job.id, refreshedAt: new Date() }); return JobStatus.SUCCESS; } async handleQueueSyncAssets(job: IEntityJob): Promise { - const library = await this.repository.get(job.id); + const library = await this.libraryRepository.get(job.id); if (!library) { return JobStatus.SKIPPED; } @@ -636,7 +613,7 @@ export class LibraryService extends BaseService { } private async findOrFail(id: string) { - const library = await this.repository.get(id); + const library = await this.libraryRepository.get(id); if (!library) { throw new BadRequestException('Library not found'); } diff --git a/server/src/services/map.service.ts b/server/src/services/map.service.ts index 3b1ee58cf124d1..860a782e79a0b2 100644 --- a/server/src/services/map.service.ts +++ b/server/src/services/map.service.ts @@ -1,18 +1,9 @@ -import { Inject } from '@nestjs/common'; import { AuthDto } from 'src/dtos/auth.dto'; import { MapMarkerDto, MapMarkerResponseDto, MapReverseGeocodeDto } from 'src/dtos/map.dto'; -import { IAlbumRepository } from 'src/interfaces/album.interface'; -import { IMapRepository } from 'src/interfaces/map.interface'; -import { IPartnerRepository } from 'src/interfaces/partner.interface'; +import { BaseService } from 'src/services/base.service'; import { getMyPartnerIds } from 'src/utils/asset.util'; -export class MapService { - constructor( - @Inject(IAlbumRepository) private albumRepository: IAlbumRepository, - @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository, - @Inject(IMapRepository) private mapRepository: IMapRepository, - ) {} - +export class MapService extends BaseService { async getMapMarkers(auth: AuthDto, options: MapMarkerDto): Promise { const userIds = [auth.user.id]; if (options.withPartners) { diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts index adb8c54f4a797e..e4fd91f363b573 100644 --- a/server/src/services/media.service.ts +++ b/server/src/services/media.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { dirname } from 'node:path'; import { StorageCore } from 'src/cores/storage.core'; import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto'; @@ -17,31 +17,17 @@ import { VideoCodec, VideoContainer, } from 'src/enum'; -import { IAssetRepository, UpsertFileOptions, WithoutProperty } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; +import { UpsertFileOptions, WithoutProperty } from 'src/interfaces/asset.interface'; import { IBaseJob, IEntityJob, - IJobRepository, JOBS_ASSET_PAGINATION_SIZE, JobItem, JobName, JobStatus, QueueName, } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { - AudioStreamInfo, - IMediaRepository, - TranscodeCommand, - VideoFormat, - VideoStreamInfo, -} from 'src/interfaces/media.interface'; -import { IMoveRepository } from 'src/interfaces/move.interface'; -import { IPersonRepository } from 'src/interfaces/person.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { AudioStreamInfo, TranscodeCommand, VideoFormat, VideoStreamInfo } from 'src/interfaces/media.interface'; import { BaseService } from 'src/services/base.service'; import { getAssetFiles } from 'src/utils/asset.util'; import { BaseConfig, ThumbnailConfig } from 'src/utils/media'; @@ -50,36 +36,9 @@ import { usePagination } from 'src/utils/pagination'; @Injectable() export class MediaService extends BaseService { - private storageCore: StorageCore; private maliOpenCL?: boolean; private devices?: string[]; - constructor( - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(IPersonRepository) private personRepository: IPersonRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(IMediaRepository) private mediaRepository: IMediaRepository, - @Inject(IStorageRepository) private storageRepository: IStorageRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(IMoveRepository) moveRepository: IMoveRepository, - @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(MediaService.name); - this.storageCore = StorageCore.create( - assetRepository, - configRepository, - cryptoRepository, - moveRepository, - personRepository, - storageRepository, - systemMetadataRepository, - this.logger, - ); - } - async handleQueueGenerateThumbnails({ force }: IBaseJob): Promise { const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { return force diff --git a/server/src/services/memory.service.ts b/server/src/services/memory.service.ts index fb1ff49f0b4562..a3b5bdc3eb3a8f 100644 --- a/server/src/services/memory.service.ts +++ b/server/src/services/memory.service.ts @@ -1,23 +1,17 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { MemoryCreateDto, MemoryResponseDto, MemoryUpdateDto, mapMemory } from 'src/dtos/memory.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IMemoryRepository } from 'src/interfaces/memory.interface'; +import { BaseService } from 'src/services/base.service'; import { checkAccess, requireAccess } from 'src/utils/access'; import { addAssets, removeAssets } from 'src/utils/asset.util'; @Injectable() -export class MemoryService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IMemoryRepository) private repository: IMemoryRepository, - ) {} - +export class MemoryService extends BaseService { async search(auth: AuthDto) { - const memories = await this.repository.search(auth.user.id); + const memories = await this.memoryRepository.search(auth.user.id); return memories.map((memory) => mapMemory(memory)); } @@ -36,7 +30,7 @@ export class MemoryService { permission: Permission.ASSET_SHARE, ids: assetIds, }); - const memory = await this.repository.create({ + const memory = await this.memoryRepository.create({ ownerId: auth.user.id, type: dto.type, data: dto.data, @@ -52,7 +46,7 @@ export class MemoryService { async update(auth: AuthDto, id: string, dto: MemoryUpdateDto): Promise { await requireAccess(this.access, { auth, permission: Permission.MEMORY_UPDATE, ids: [id] }); - const memory = await this.repository.update({ + const memory = await this.memoryRepository.update({ id, isSaved: dto.isSaved, memoryAt: dto.memoryAt, @@ -64,18 +58,18 @@ export class MemoryService { async remove(auth: AuthDto, id: string): Promise { await requireAccess(this.access, { auth, permission: Permission.MEMORY_DELETE, ids: [id] }); - await this.repository.delete(id); + await this.memoryRepository.delete(id); } async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { await requireAccess(this.access, { auth, permission: Permission.MEMORY_READ, ids: [id] }); - const repos = { access: this.access, bulk: this.repository }; + const repos = { access: this.access, bulk: this.memoryRepository }; const results = await addAssets(auth, repos, { parentId: id, assetIds: dto.ids }); const hasSuccess = results.find(({ success }) => success); if (hasSuccess) { - await this.repository.update({ id, updatedAt: new Date() }); + await this.memoryRepository.update({ id, updatedAt: new Date() }); } return results; @@ -84,7 +78,7 @@ export class MemoryService { async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { await requireAccess(this.access, { auth, permission: Permission.MEMORY_UPDATE, ids: [id] }); - const repos = { access: this.access, bulk: this.repository }; + const repos = { access: this.access, bulk: this.memoryRepository }; const results = await removeAssets(auth, repos, { parentId: id, assetIds: dto.ids, @@ -93,14 +87,14 @@ export class MemoryService { const hasSuccess = results.find(({ success }) => success); if (hasSuccess) { - await this.repository.update({ id, updatedAt: new Date() }); + await this.memoryRepository.update({ id, updatedAt: new Date() }); } return results; } private async findOrFail(id: string) { - const memory = await this.repository.get(id); + const memory = await this.memoryRepository.get(id); if (!memory) { throw new BadRequestException('Memory not found'); } diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 3995c72f770e5a..9d66d90bd99efd 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { ContainerDirectoryItem, ExifDateTime, Maybe, Tags } from 'exiftool-vendored'; import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime'; import _ from 'lodash'; @@ -13,32 +13,20 @@ import { AssetEntity } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { AssetType, SourceType } from 'src/enum'; -import { IAlbumRepository } from 'src/interfaces/album.interface'; -import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface'; -import { ArgOf, IEventRepository } from 'src/interfaces/event.interface'; +import { WithoutProperty } from 'src/interfaces/asset.interface'; +import { DatabaseLock } from 'src/interfaces/database.interface'; +import { ArgOf } from 'src/interfaces/event.interface'; import { IBaseJob, IEntityJob, - IJobRepository, ISidecarWriteJob, JobName, JOBS_ASSET_PAGINATION_SIZE, JobStatus, QueueName, } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IMapRepository, ReverseGeocodeResult } from 'src/interfaces/map.interface'; -import { IMediaRepository } from 'src/interfaces/media.interface'; -import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface'; -import { IMoveRepository } from 'src/interfaces/move.interface'; -import { IPersonRepository } from 'src/interfaces/person.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { ITagRepository } from 'src/interfaces/tag.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; +import { ReverseGeocodeResult } from 'src/interfaces/map.interface'; +import { ImmichTags } from 'src/interfaces/metadata.interface'; import { BaseService } from 'src/services/base.service'; import { isFaceImportEnabled } from 'src/utils/misc'; import { usePagination } from 'src/utils/pagination'; @@ -99,41 +87,6 @@ const validateRange = (value: number | undefined, min: number, max: number): Non @Injectable() export class MetadataService extends BaseService { - private storageCore: StorageCore; - - constructor( - @Inject(IAlbumRepository) private albumRepository: IAlbumRepository, - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, - @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(IMapRepository) private mapRepository: IMapRepository, - @Inject(IMediaRepository) private mediaRepository: IMediaRepository, - @Inject(IMetadataRepository) private repository: IMetadataRepository, - @Inject(IMoveRepository) moveRepository: IMoveRepository, - @Inject(IPersonRepository) private personRepository: IPersonRepository, - @Inject(IStorageRepository) private storageRepository: IStorageRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(ITagRepository) private tagRepository: ITagRepository, - @Inject(IUserRepository) private userRepository: IUserRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(MetadataService.name); - this.storageCore = StorageCore.create( - assetRepository, - configRepository, - cryptoRepository, - moveRepository, - personRepository, - storageRepository, - systemMetadataRepository, - this.logger, - ); - } - @OnEvent({ name: 'app.bootstrap' }) async onBootstrap(app: ArgOf<'app.bootstrap'>) { if (app !== 'microservices') { @@ -145,7 +98,7 @@ export class MetadataService extends BaseService { @OnEvent({ name: 'app.shutdown' }) async onShutdown() { - await this.repository.teardown(); + await this.metadataRepository.teardown(); } @OnEvent({ name: 'config.update' }) @@ -372,7 +325,7 @@ export class MetadataService extends BaseService { return JobStatus.SKIPPED; } - await this.repository.writeTags(sidecarPath, exif); + await this.metadataRepository.writeTags(sidecarPath, exif); if (!asset.sidecarPath) { await this.assetRepository.update({ id, sidecarPath }); @@ -382,8 +335,8 @@ export class MetadataService extends BaseService { } private async getExifTags(asset: AssetEntity): Promise { - const mediaTags = await this.repository.readTags(asset.originalPath); - const sidecarTags = asset.sidecarPath ? await this.repository.readTags(asset.sidecarPath) : {}; + const mediaTags = await this.metadataRepository.readTags(asset.originalPath); + const sidecarTags = asset.sidecarPath ? await this.metadataRepository.readTags(asset.sidecarPath) : {}; const videoTags = asset.type === AssetType.VIDEO ? await this.getVideoTags(asset.originalPath) : {}; // make sure dates comes from sidecar @@ -467,11 +420,11 @@ export class MetadataService extends BaseService { // Samsung MotionPhoto video extraction // HEIC-encoded if (hasMotionPhotoVideo) { - video = await this.repository.extractBinaryTag(asset.originalPath, 'MotionPhotoVideo'); + video = await this.metadataRepository.extractBinaryTag(asset.originalPath, 'MotionPhotoVideo'); } // JPEG-encoded; HEIC also contains these tags, so this conditional must come second else if (hasEmbeddedVideoFile) { - video = await this.repository.extractBinaryTag(asset.originalPath, 'EmbeddedVideoFile'); + video = await this.metadataRepository.extractBinaryTag(asset.originalPath, 'EmbeddedVideoFile'); } // Default video extraction else { @@ -481,7 +434,7 @@ export class MetadataService extends BaseService { length, }); } - const checksum = this.cryptoRepository.hashSha1(video); + const checksum = this.crypto.hashSha1(video); let motionAsset = await this.assetRepository.getByChecksum({ ownerId: asset.ownerId, @@ -501,7 +454,7 @@ export class MetadataService extends BaseService { this.logger.log(`Hid unlinked motion photo video asset (${motionAsset.id})`); } } else { - const motionAssetId = this.cryptoRepository.randomUUID(); + const motionAssetId = this.crypto.randomUUID(); const createdAt = asset.fileCreatedAt ?? asset.createdAt; motionAsset = await this.assetRepository.create({ id: motionAssetId, @@ -573,10 +526,10 @@ export class MetadataService extends BaseService { const imageWidth = tags.RegionInfo.AppliedToDimensions.W; const imageHeight = tags.RegionInfo.AppliedToDimensions.H; const loweredName = region.Name.toLowerCase(); - const personId = existingNameMap.get(loweredName) || this.cryptoRepository.randomUUID(); + const personId = existingNameMap.get(loweredName) || this.crypto.randomUUID(); const face = { - id: this.cryptoRepository.randomUUID(), + id: this.crypto.randomUUID(), personId, assetId: asset.id, imageWidth, diff --git a/server/src/services/notification.service.ts b/server/src/services/notification.service.ts index dce13e5f6c60e2..f6b338d79e716a 100644 --- a/server/src/services/notification.service.ts +++ b/server/src/services/notification.service.ts @@ -1,25 +1,18 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { DEFAULT_EXTERNAL_DOMAIN } from 'src/constants'; import { OnEvent } from 'src/decorators'; import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto'; import { AlbumEntity } from 'src/entities/album.entity'; -import { IAlbumRepository } from 'src/interfaces/album.interface'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ArgOf, IEventRepository } from 'src/interfaces/event.interface'; +import { ArgOf } from 'src/interfaces/event.interface'; import { IEmailJob, - IJobRepository, INotifyAlbumInviteJob, INotifyAlbumUpdateJob, INotifySignupJob, JobName, JobStatus, } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { EmailImageAttachment, EmailTemplate, INotificationRepository } from 'src/interfaces/notification.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; +import { EmailImageAttachment, EmailTemplate } from 'src/interfaces/notification.interface'; import { BaseService } from 'src/services/base.service'; import { getAssetFiles } from 'src/utils/asset.util'; import { getFilenameExtension } from 'src/utils/file'; @@ -28,21 +21,6 @@ import { getPreferences } from 'src/utils/preferences'; @Injectable() export class NotificationService extends BaseService { - constructor( - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(INotificationRepository) private notificationRepository: INotificationRepository, - @Inject(IUserRepository) private userRepository: IUserRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IAlbumRepository) private albumRepository: IAlbumRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(NotificationService.name); - } - @OnEvent({ name: 'config.update' }) onConfigUpdate({ oldConfig, newConfig }: ArgOf<'config.update'>) { this.eventRepository.clientBroadcast('on_config_update'); diff --git a/server/src/services/partner.service.ts b/server/src/services/partner.service.ts index 4b7cd4c516e428..ee420eeefd7582 100644 --- a/server/src/services/partner.service.ts +++ b/server/src/services/partner.service.ts @@ -1,43 +1,38 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { AuthDto } from 'src/dtos/auth.dto'; import { PartnerResponseDto, PartnerSearchDto, UpdatePartnerDto } from 'src/dtos/partner.dto'; import { mapUser } from 'src/dtos/user.dto'; import { PartnerEntity } from 'src/entities/partner.entity'; import { Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IPartnerRepository, PartnerDirection, PartnerIds } from 'src/interfaces/partner.interface'; +import { PartnerDirection, PartnerIds } from 'src/interfaces/partner.interface'; +import { BaseService } from 'src/services/base.service'; import { requireAccess } from 'src/utils/access'; @Injectable() -export class PartnerService { - constructor( - @Inject(IPartnerRepository) private repository: IPartnerRepository, - @Inject(IAccessRepository) private access: IAccessRepository, - ) {} - +export class PartnerService extends BaseService { async create(auth: AuthDto, sharedWithId: string): Promise { const partnerId: PartnerIds = { sharedById: auth.user.id, sharedWithId }; - const exists = await this.repository.get(partnerId); + const exists = await this.partnerRepository.get(partnerId); if (exists) { throw new BadRequestException(`Partner already exists`); } - const partner = await this.repository.create(partnerId); + const partner = await this.partnerRepository.create(partnerId); return this.mapPartner(partner, PartnerDirection.SharedBy); } async remove(auth: AuthDto, sharedWithId: string): Promise { const partnerId: PartnerIds = { sharedById: auth.user.id, sharedWithId }; - const partner = await this.repository.get(partnerId); + const partner = await this.partnerRepository.get(partnerId); if (!partner) { throw new BadRequestException('Partner not found'); } - await this.repository.remove(partner); + await this.partnerRepository.remove(partner); } async search(auth: AuthDto, { direction }: PartnerSearchDto): Promise { - const partners = await this.repository.getAll(auth.user.id); + const partners = await this.partnerRepository.getAll(auth.user.id); const key = direction === PartnerDirection.SharedBy ? 'sharedById' : 'sharedWithId'; return partners .filter((partner) => partner.sharedBy && partner.sharedWith) // Filter out soft deleted users @@ -49,7 +44,7 @@ export class PartnerService { await requireAccess(this.access, { auth, permission: Permission.PARTNER_UPDATE, ids: [sharedById] }); const partnerId: PartnerIds = { sharedById, sharedWithId: auth.user.id }; - const entity = await this.repository.update({ ...partnerId, inTimeline: dto.inTimeline }); + const entity = await this.partnerRepository.update({ ...partnerId, inTimeline: dto.inTimeline }); return this.mapPartner(entity, PartnerDirection.SharedWith); } diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index 3b71d3504e33ba..4b378c2fb78e20 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { FACE_THUMBNAIL_SIZE } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; import { BulkIdErrorReason, BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto'; @@ -31,15 +31,11 @@ import { SourceType, SystemMetadataKey, } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; +import { WithoutProperty } from 'src/interfaces/asset.interface'; import { IBaseJob, IDeferrableJob, IEntityJob, - IJobRepository, INightlyJob, JOBS_ASSET_PAGINATION_SIZE, JobItem, @@ -47,14 +43,9 @@ import { JobStatus, QueueName, } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { BoundingBox, IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; -import { CropOptions, IMediaRepository, ImageDimensions, InputDimensions } from 'src/interfaces/media.interface'; -import { IMoveRepository } from 'src/interfaces/move.interface'; -import { IPersonRepository, UpdateFacesData } from 'src/interfaces/person.interface'; -import { ISearchRepository } from 'src/interfaces/search.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { BoundingBox } from 'src/interfaces/machine-learning.interface'; +import { CropOptions, ImageDimensions, InputDimensions } from 'src/interfaces/media.interface'; +import { UpdateFacesData } from 'src/interfaces/person.interface'; import { BaseService } from 'src/services/base.service'; import { checkAccess, requireAccess } from 'src/utils/access'; import { getAssetFiles } from 'src/utils/asset.util'; @@ -66,37 +57,6 @@ import { IsNull } from 'typeorm'; @Injectable() export class PersonService extends BaseService { - private storageCore: StorageCore; - - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(IMachineLearningRepository) private machineLearningRepository: IMachineLearningRepository, - @Inject(IMoveRepository) moveRepository: IMoveRepository, - @Inject(IMediaRepository) private mediaRepository: IMediaRepository, - @Inject(IPersonRepository) private repository: IPersonRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(IStorageRepository) private storageRepository: IStorageRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(ISearchRepository) private smartInfoRepository: ISearchRepository, - @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(PersonService.name); - this.storageCore = StorageCore.create( - assetRepository, - configRepository, - cryptoRepository, - moveRepository, - repository, - storageRepository, - systemMetadataRepository, - this.logger, - ); - } - async getAll(auth: AuthDto, dto: PersonSearchDto): Promise { const { withHidden = false, page, size } = dto; const pagination = { @@ -105,11 +65,11 @@ export class PersonService extends BaseService { }; const { machineLearning } = await this.getConfig({ withCache: false }); - const { items, hasNextPage } = await this.repository.getAllForUser(pagination, auth.user.id, { + const { items, hasNextPage } = await this.personRepository.getAllForUser(pagination, auth.user.id, { minimumFaceCount: machineLearning.facialRecognition.minFaces, withHidden, }); - const { total, hidden } = await this.repository.getNumberOfPeople(auth.user.id); + const { total, hidden } = await this.personRepository.getNumberOfPeople(auth.user.id); return { people: items.map((person) => mapPerson(person)), @@ -125,7 +85,7 @@ export class PersonService extends BaseService { const result: PersonResponseDto[] = []; const changeFeaturePhoto: string[] = []; for (const data of dto.data) { - const faces = await this.repository.getFacesByIds([{ personId: data.personId, assetId: data.assetId }]); + const faces = await this.personRepository.getFacesByIds([{ personId: data.personId, assetId: data.assetId }]); for (const face of faces) { await requireAccess(this.access, { auth, permission: Permission.PERSON_CREATE, ids: [face.id] }); @@ -136,7 +96,7 @@ export class PersonService extends BaseService { changeFeaturePhoto.push(face.person.id); } - await this.repository.reassignFace(face.id, personId); + await this.personRepository.reassignFace(face.id, personId); } result.push(person); @@ -151,10 +111,10 @@ export class PersonService extends BaseService { async reassignFacesById(auth: AuthDto, personId: string, dto: FaceDto): Promise { await requireAccess(this.access, { auth, permission: Permission.PERSON_UPDATE, ids: [personId] }); await requireAccess(this.access, { auth, permission: Permission.PERSON_CREATE, ids: [dto.id] }); - const face = await this.repository.getFaceById(dto.id); + const face = await this.personRepository.getFaceById(dto.id); const person = await this.findOrFail(personId); - await this.repository.reassignFace(face.id, personId); + await this.personRepository.reassignFace(face.id, personId); if (person.faceAssetId === null) { await this.createNewFeaturePhoto([person.id]); } @@ -167,7 +127,7 @@ export class PersonService extends BaseService { async getFacesById(auth: AuthDto, dto: FaceDto): Promise { await requireAccess(this.access, { auth, permission: Permission.ASSET_READ, ids: [dto.id] }); - const faces = await this.repository.getFaces(dto.id); + const faces = await this.personRepository.getFaces(dto.id); return faces.map((asset) => mapFaces(asset, auth)); } @@ -178,10 +138,10 @@ export class PersonService extends BaseService { const jobs: JobItem[] = []; for (const personId of changeFeaturePhoto) { - const assetFace = await this.repository.getRandomFace(personId); + const assetFace = await this.personRepository.getRandomFace(personId); if (assetFace !== null) { - await this.repository.update({ id: personId, faceAssetId: assetFace.id }); + await this.personRepository.update({ id: personId, faceAssetId: assetFace.id }); jobs.push({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: personId } }); } } @@ -196,12 +156,12 @@ export class PersonService extends BaseService { async getStatistics(auth: AuthDto, id: string): Promise { await requireAccess(this.access, { auth, permission: Permission.PERSON_READ, ids: [id] }); - return this.repository.getStatistics(id); + return this.personRepository.getStatistics(id); } async getThumbnail(auth: AuthDto, id: string): Promise { await requireAccess(this.access, { auth, permission: Permission.PERSON_READ, ids: [id] }); - const person = await this.repository.getById(id); + const person = await this.personRepository.getById(id); if (!person || !person.thumbnailPath) { throw new NotFoundException(); } @@ -215,12 +175,12 @@ export class PersonService extends BaseService { async getAssets(auth: AuthDto, id: string): Promise { await requireAccess(this.access, { auth, permission: Permission.PERSON_READ, ids: [id] }); - const assets = await this.repository.getAssets(id); + const assets = await this.personRepository.getAssets(id); return assets.map((asset) => mapAsset(asset)); } create(auth: AuthDto, dto: PersonCreateDto): Promise { - return this.repository.create({ + return this.personRepository.create({ ownerId: auth.user.id, name: dto.name, birthDate: dto.birthDate, @@ -236,7 +196,7 @@ export class PersonService extends BaseService { let faceId: string | undefined = undefined; if (assetId) { await requireAccess(this.access, { auth, permission: Permission.ASSET_READ, ids: [assetId] }); - const [face] = await this.repository.getFacesByIds([{ personId: id, assetId }]); + const [face] = await this.personRepository.getFacesByIds([{ personId: id, assetId }]); if (!face) { throw new BadRequestException('Invalid assetId for feature face'); } @@ -244,7 +204,7 @@ export class PersonService extends BaseService { faceId = face.id; } - const person = await this.repository.update({ id, faceAssetId: faceId, name, birthDate, isHidden }); + const person = await this.personRepository.update({ id, faceAssetId: faceId, name, birthDate, isHidden }); if (assetId) { await this.jobRepository.queue({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id } }); @@ -274,12 +234,12 @@ export class PersonService extends BaseService { private async delete(people: PersonEntity[]) { await Promise.all(people.map((person) => this.storageRepository.unlink(person.thumbnailPath))); - await this.repository.delete(people); + await this.personRepository.delete(people); this.logger.debug(`Deleted ${people.length} people`); } async handlePersonCleanup(): Promise { - const people = await this.repository.getAllWithoutFaces(); + const people = await this.personRepository.getAllWithoutFaces(); await this.delete(people); return JobStatus.SUCCESS; } @@ -291,7 +251,7 @@ export class PersonService extends BaseService { } if (force) { - await this.repository.deleteFaces({ sourceType: SourceType.MACHINE_LEARNING }); + await this.personRepository.deleteFaces({ sourceType: SourceType.MACHINE_LEARNING }); await this.handlePersonCleanup(); } @@ -350,7 +310,7 @@ export class PersonService extends BaseService { await this.jobRepository.queue({ name: JobName.QUEUE_FACIAL_RECOGNITION, data: { force: false } }); const mappedFaces: Partial[] = []; for (const face of faces) { - const faceId = this.cryptoRepository.randomUUID(); + const faceId = this.crypto.randomUUID(); mappedFaces.push({ id: faceId, assetId: asset.id, @@ -364,7 +324,7 @@ export class PersonService extends BaseService { }); } - const faceIds = await this.repository.createFaces(mappedFaces); + const faceIds = await this.personRepository.createFaces(mappedFaces); await this.jobRepository.queueAll(faceIds.map((id) => ({ name: JobName.FACIAL_RECOGNITION, data: { id } }))); } @@ -387,7 +347,7 @@ export class PersonService extends BaseService { if (nightly) { const [state, latestFaceDate] = await Promise.all([ this.systemMetadataRepository.get(SystemMetadataKey.FACIAL_RECOGNITION_STATE), - this.repository.getLatestFaceDate(), + this.personRepository.getLatestFaceDate(), ]); if (state?.lastRun && latestFaceDate && state.lastRun > latestFaceDate) { @@ -399,7 +359,7 @@ export class PersonService extends BaseService { const { waiting } = await this.jobRepository.getJobCounts(QueueName.FACIAL_RECOGNITION); if (force) { - await this.repository.unassignFaces({ sourceType: SourceType.MACHINE_LEARNING }); + await this.personRepository.unassignFaces({ sourceType: SourceType.MACHINE_LEARNING }); await this.handlePersonCleanup(); } else if (waiting) { this.logger.debug( @@ -410,7 +370,7 @@ export class PersonService extends BaseService { const lastRun = new Date().toISOString(); const facePagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => - this.repository.getAllFaces(pagination, { + this.personRepository.getAllFaces(pagination, { where: force ? undefined : { personId: IsNull(), sourceType: IsNull() }, }), ); @@ -432,7 +392,7 @@ export class PersonService extends BaseService { return JobStatus.SKIPPED; } - const face = await this.repository.getFaceByIdWithAssets( + const face = await this.personRepository.getFaceByIdWithAssets( id, { person: true, asset: true, faceSearch: true }, { id: true, personId: true, sourceType: true, faceSearch: { embedding: true } }, @@ -457,7 +417,7 @@ export class PersonService extends BaseService { return JobStatus.SKIPPED; } - const matches = await this.smartInfoRepository.searchFaces({ + const matches = await this.searchRepository.searchFaces({ userIds: [face.asset.ownerId], embedding: face.faceSearch.embedding, maxDistance: machineLearning.facialRecognition.maxDistance, @@ -481,7 +441,7 @@ export class PersonService extends BaseService { let personId = matches.find((match) => match.face.personId)?.face.personId; if (!personId) { - const matchWithPerson = await this.smartInfoRepository.searchFaces({ + const matchWithPerson = await this.searchRepository.searchFaces({ userIds: [face.asset.ownerId], embedding: face.faceSearch.embedding, maxDistance: machineLearning.facialRecognition.maxDistance, @@ -496,21 +456,21 @@ export class PersonService extends BaseService { if (isCore && !personId) { this.logger.log(`Creating new person for face ${id}`); - const newPerson = await this.repository.create({ ownerId: face.asset.ownerId, faceAssetId: face.id }); + const newPerson = await this.personRepository.create({ ownerId: face.asset.ownerId, faceAssetId: face.id }); await this.jobRepository.queue({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: newPerson.id } }); personId = newPerson.id; } if (personId) { this.logger.debug(`Assigning face ${id} to person ${personId}`); - await this.repository.reassignFaces({ faceIds: [id], newPersonId: personId }); + await this.personRepository.reassignFaces({ faceIds: [id], newPersonId: personId }); } return JobStatus.SUCCESS; } async handlePersonMigration({ id }: IEntityJob): Promise { - const person = await this.repository.getById(id); + const person = await this.personRepository.getById(id); if (!person) { return JobStatus.FAILED; } @@ -526,13 +486,13 @@ export class PersonService extends BaseService { return JobStatus.SKIPPED; } - const person = await this.repository.getById(data.id); + const person = await this.personRepository.getById(data.id); if (!person?.faceAssetId) { this.logger.error(`Could not generate person thumbnail: person ${person?.id} has no face asset`); return JobStatus.FAILED; } - const face = await this.repository.getFaceByIdWithAssets(person.faceAssetId); + const face = await this.personRepository.getFaceByIdWithAssets(person.faceAssetId); if (face === null) { this.logger.error(`Could not generate person thumbnail: face ${person.faceAssetId} not found`); return JobStatus.FAILED; @@ -572,7 +532,7 @@ export class PersonService extends BaseService { }; await this.mediaRepository.generateThumbnail(inputPath, thumbnailOptions, thumbnailPath); - await this.repository.update({ id: person.id, thumbnailPath }); + await this.personRepository.update({ id: person.id, thumbnailPath }); return JobStatus.SUCCESS; } @@ -603,7 +563,7 @@ export class PersonService extends BaseService { } try { - const mergePerson = await this.repository.getById(mergeId); + const mergePerson = await this.personRepository.getById(mergeId); if (!mergePerson) { results.push({ id: mergeId, success: false, error: BulkIdErrorReason.NOT_FOUND }); continue; @@ -619,14 +579,14 @@ export class PersonService extends BaseService { } if (Object.keys(update).length > 0) { - primaryPerson = await this.repository.update({ id: primaryPerson.id, ...update }); + primaryPerson = await this.personRepository.update({ id: primaryPerson.id, ...update }); } const mergeName = mergePerson.name || mergePerson.id; const mergeData: UpdateFacesData = { oldPersonId: mergeId, newPersonId: id }; this.logger.log(`Merging ${mergeName} into ${primaryName}`); - await this.repository.reassignFaces(mergeData); + await this.personRepository.reassignFaces(mergeData); await this.delete([mergePerson]); this.logger.log(`Merged ${mergeName} into ${primaryName}`); @@ -640,7 +600,7 @@ export class PersonService extends BaseService { } private async findOrFail(id: string) { - const person = await this.repository.getById(id); + const person = await this.personRepository.getById(id); if (!person) { throw new BadRequestException('Person not found'); } diff --git a/server/src/services/search.service.ts b/server/src/services/search.service.ts index b878b4e89808e2..03ffbe97db14e1 100644 --- a/server/src/services/search.service.ts +++ b/server/src/services/search.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { AssetMapOptions, AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { PersonResponseDto } from 'src/dtos/person.dto'; @@ -16,34 +16,13 @@ import { } from 'src/dtos/search.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { AssetOrder } from 'src/enum'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; -import { IPartnerRepository } from 'src/interfaces/partner.interface'; -import { IPersonRepository } from 'src/interfaces/person.interface'; -import { ISearchRepository, SearchExploreItem } from 'src/interfaces/search.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { SearchExploreItem } from 'src/interfaces/search.interface'; import { BaseService } from 'src/services/base.service'; import { getMyPartnerIds } from 'src/utils/asset.util'; import { isSmartSearchEnabled } from 'src/utils/misc'; @Injectable() export class SearchService extends BaseService { - constructor( - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository, - @Inject(IPersonRepository) private personRepository: IPersonRepository, - @Inject(ISearchRepository) private searchRepository: ISearchRepository, - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(SearchService.name); - } - async searchPerson(auth: AuthDto, dto: SearchPeopleDto): Promise { return this.personRepository.getByName(auth.user.id, dto.name, { withHidden: dto.withHidden }); } @@ -108,7 +87,11 @@ export class SearchService extends BaseService { const userIds = await this.getUserIdsToSearch(auth); - const embedding = await this.machineLearning.encodeText(machineLearning.url, dto.query, machineLearning.clip); + const embedding = await this.machineLearningRepository.encodeText( + machineLearning.url, + dto.query, + machineLearning.clip, + ); const page = dto.page ?? 1; const size = dto.size || 100; const { hasNextPage, items } = await this.searchRepository.searchSmart( diff --git a/server/src/services/server.service.ts b/server/src/services/server.service.ts index ffab0c5a893dc8..a04df8f5e99e34 100644 --- a/server/src/services/server.service.ts +++ b/server/src/services/server.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { getBuildMetadata, getServerLicensePublicKey } from 'src/config'; import { serverVersion } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; @@ -15,13 +15,7 @@ import { UsageByUserDto, } from 'src/dtos/server.dto'; import { StorageFolder, SystemMetadataKey } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { IUserRepository, UserStatsQueryResponse } from 'src/interfaces/user.interface'; +import { UserStatsQueryResponse } from 'src/interfaces/user.interface'; import { BaseService } from 'src/services/base.service'; import { asHumanReadable } from 'src/utils/bytes'; import { mimeTypes } from 'src/utils/mime-types'; @@ -29,19 +23,6 @@ import { isDuplicateDetectionEnabled, isFacialRecognitionEnabled, isSmartSearchE @Injectable() export class ServerService extends BaseService { - constructor( - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(IUserRepository) private userRepository: IUserRepository, - @Inject(IStorageRepository) private storageRepository: IStorageRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(IServerInfoRepository) private serverInfoRepository: IServerInfoRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(ServerService.name); - } - @OnEvent({ name: 'app.bootstrap' }) async onBootstrap(): Promise { const featureFlags = await this.getFeatures(); @@ -181,11 +162,7 @@ export class ServerService extends BaseService { if (!dto.licenseKey.startsWith('IMSV-')) { throw new BadRequestException('Invalid license key'); } - const licenseValid = this.cryptoRepository.verifySha256( - dto.licenseKey, - dto.activationKey, - getServerLicensePublicKey(), - ); + const licenseValid = this.crypto.verifySha256(dto.licenseKey, dto.activationKey, getServerLicensePublicKey()); if (!licenseValid) { throw new BadRequestException('Invalid license key'); diff --git a/server/src/services/session.service.ts b/server/src/services/session.service.ts index 47abf3c3802461..4cec2a2d4b8e51 100644 --- a/server/src/services/session.service.ts +++ b/server/src/services/session.service.ts @@ -1,24 +1,14 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; import { AuthDto } from 'src/dtos/auth.dto'; import { SessionResponseDto, mapSession } from 'src/dtos/session.dto'; import { Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; import { JobStatus } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { ISessionRepository } from 'src/interfaces/session.interface'; +import { BaseService } from 'src/services/base.service'; import { requireAccess } from 'src/utils/access'; @Injectable() -export class SessionService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, - @Inject(ISessionRepository) private sessionRepository: ISessionRepository, - ) { - this.logger.setContext(SessionService.name); - } - +export class SessionService extends BaseService { async handleCleanup() { const sessions = await this.sessionRepository.search({ updatedBefore: DateTime.now().minus({ days: 90 }).toJSDate(), diff --git a/server/src/services/shared-link.service.ts b/server/src/services/shared-link.service.ts index f2b0ea3c659ab6..2b664981a2ef85 100644 --- a/server/src/services/shared-link.service.ts +++ b/server/src/services/shared-link.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, ForbiddenException, Inject, Injectable, UnauthorizedException } from '@nestjs/common'; +import { BadRequestException, ForbiddenException, Injectable, UnauthorizedException } from '@nestjs/common'; import { DEFAULT_EXTERNAL_DOMAIN } from 'src/constants'; import { AssetIdErrorReason, AssetIdsResponseDto } from 'src/dtos/asset-ids.response.dto'; import { AssetIdsDto } from 'src/dtos/asset.dto'; @@ -14,32 +14,14 @@ import { import { AssetEntity } from 'src/entities/asset.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { Permission, SharedLinkType } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { BaseService } from 'src/services/base.service'; import { checkAccess, requireAccess } from 'src/utils/access'; import { OpenGraphTags } from 'src/utils/misc'; @Injectable() export class SharedLinkService extends BaseService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - @Inject(ISharedLinkRepository) private repository: ISharedLinkRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(SharedLinkService.name); - } - getAll(auth: AuthDto): Promise { - return this.repository.getAll(auth.user.id).then((links) => links.map((link) => mapSharedLink(link))); + return this.sharedLinkRepository.getAll(auth.user.id).then((links) => links.map((link) => mapSharedLink(link))); } async getMine(auth: AuthDto, dto: SharedLinkPasswordDto): Promise { @@ -82,8 +64,8 @@ export class SharedLinkService extends BaseService { } } - const sharedLink = await this.repository.create({ - key: this.cryptoRepository.randomBytes(50), + const sharedLink = await this.sharedLinkRepository.create({ + key: this.crypto.randomBytes(50), userId: auth.user.id, type: dto.type, albumId: dto.albumId || null, @@ -101,7 +83,7 @@ export class SharedLinkService extends BaseService { async update(auth: AuthDto, id: string, dto: SharedLinkEditDto) { await this.findOrFail(auth.user.id, id); - const sharedLink = await this.repository.update({ + const sharedLink = await this.sharedLinkRepository.update({ id, userId: auth.user.id, description: dto.description, @@ -116,12 +98,12 @@ export class SharedLinkService extends BaseService { async remove(auth: AuthDto, id: string): Promise { const sharedLink = await this.findOrFail(auth.user.id, id); - await this.repository.remove(sharedLink); + await this.sharedLinkRepository.remove(sharedLink); } // TODO: replace `userId` with permissions and access control checks private async findOrFail(userId: string, id: string) { - const sharedLink = await this.repository.get(userId, id); + const sharedLink = await this.sharedLinkRepository.get(userId, id); if (!sharedLink) { throw new BadRequestException('Shared link not found'); } @@ -161,7 +143,7 @@ export class SharedLinkService extends BaseService { sharedLink.assets.push({ id: assetId } as AssetEntity); } - await this.repository.update(sharedLink); + await this.sharedLinkRepository.update(sharedLink); return results; } @@ -185,7 +167,7 @@ export class SharedLinkService extends BaseService { sharedLink.assets = sharedLink.assets.filter((asset) => asset.id !== assetId); } - await this.repository.update(sharedLink); + await this.sharedLinkRepository.update(sharedLink); return results; } @@ -215,7 +197,7 @@ export class SharedLinkService extends BaseService { } private validateAndRefreshToken(sharedLink: SharedLinkEntity, dto: SharedLinkPasswordDto): string { - const token = this.cryptoRepository.hashSha256(`${sharedLink.id}-${sharedLink.password}`); + const token = this.crypto.hashSha256(`${sharedLink.id}-${sharedLink.password}`); const sharedLinkTokens = dto.token?.split(',') || []; if (sharedLink.password !== dto.password && !sharedLinkTokens.includes(token)) { throw new UnauthorizedException('Invalid password'); diff --git a/server/src/services/smart-info.service.ts b/server/src/services/smart-info.service.ts index 6f24dafbfee523..66c6499940b2f1 100644 --- a/server/src/services/smart-info.service.ts +++ b/server/src/services/smart-info.service.ts @@ -1,23 +1,17 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { SystemConfig } from 'src/config'; import { OnEvent } from 'src/decorators'; -import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface'; +import { WithoutProperty } from 'src/interfaces/asset.interface'; +import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; import { IBaseJob, IEntityJob, - IJobRepository, JOBS_ASSET_PAGINATION_SIZE, JobName, JobStatus, QueueName, } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; -import { ISearchRepository } from 'src/interfaces/search.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { BaseService } from 'src/services/base.service'; import { getAssetFiles } from 'src/utils/asset.util'; import { getCLIPModelInfo, isSmartSearchEnabled } from 'src/utils/misc'; @@ -25,20 +19,6 @@ import { usePagination } from 'src/utils/pagination'; @Injectable() export class SmartInfoService extends BaseService { - constructor( - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository, - @Inject(ISearchRepository) private repository: ISearchRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(SmartInfoService.name); - } - @OnEvent({ name: 'app.bootstrap' }) async onBootstrap(app: ArgOf<'app.bootstrap'>) { if (app !== 'microservices') { @@ -72,7 +52,7 @@ export class SmartInfoService extends BaseService { await this.databaseRepository.withLock(DatabaseLock.CLIPDimSize, async () => { const { dimSize } = getCLIPModelInfo(newConfig.machineLearning.clip.modelName); - const dbDimSize = await this.repository.getDimensionSize(); + const dbDimSize = await this.searchRepository.getDimensionSize(); this.logger.verbose(`Current database CLIP dimension size is ${dbDimSize}`); const modelChange = @@ -93,10 +73,10 @@ export class SmartInfoService extends BaseService { `Dimension size of model ${newConfig.machineLearning.clip.modelName} is ${dimSize}, but database expects ${dbDimSize}.`, ); this.logger.log(`Updating database CLIP dimension size to ${dimSize}.`); - await this.repository.setDimensionSize(dimSize); + await this.searchRepository.setDimensionSize(dimSize); this.logger.log(`Successfully updated database CLIP dimension size from ${dbDimSize} to ${dimSize}.`); } else { - await this.repository.deleteAllSearchEmbeddings(); + await this.searchRepository.deleteAllSearchEmbeddings(); } if (!isPaused) { @@ -112,7 +92,7 @@ export class SmartInfoService extends BaseService { } if (force) { - await this.repository.deleteAllSearchEmbeddings(); + await this.searchRepository.deleteAllSearchEmbeddings(); } const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { @@ -150,7 +130,7 @@ export class SmartInfoService extends BaseService { return JobStatus.FAILED; } - const embedding = await this.machineLearning.encodeImage( + const embedding = await this.machineLearningRepository.encodeImage( machineLearning.url, previewFile.path, machineLearning.clip, @@ -161,7 +141,7 @@ export class SmartInfoService extends BaseService { await this.databaseRepository.wait(DatabaseLock.CLIPDimSize); } - await this.repository.upsert(asset.id, embedding); + await this.searchRepository.upsert(asset.id, embedding); return JobStatus.SUCCESS; } diff --git a/server/src/services/stack.service.ts b/server/src/services/stack.service.ts index 29a598d4b413a3..e6485c82313b16 100644 --- a/server/src/services/stack.service.ts +++ b/server/src/services/stack.service.ts @@ -1,21 +1,13 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { StackCreateDto, StackResponseDto, StackSearchDto, StackUpdateDto, mapStack } from 'src/dtos/stack.dto'; import { Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IEventRepository } from 'src/interfaces/event.interface'; -import { IStackRepository } from 'src/interfaces/stack.interface'; +import { BaseService } from 'src/services/base.service'; import { requireAccess } from 'src/utils/access'; @Injectable() -export class StackService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(IStackRepository) private stackRepository: IStackRepository, - ) {} - +export class StackService extends BaseService { async search(auth: AuthDto, dto: StackSearchDto): Promise { const stacks = await this.stackRepository.search({ ownerId: auth.user.id, diff --git a/server/src/services/storage-template.service.spec.ts b/server/src/services/storage-template.service.spec.ts index aa127c2afc64b2..7cd9ff4614c63c 100644 --- a/server/src/services/storage-template.service.spec.ts +++ b/server/src/services/storage-template.service.spec.ts @@ -4,13 +4,9 @@ import { AssetEntity } from 'src/entities/asset.entity'; import { AssetPathType } from 'src/enum'; import { IAlbumRepository } from 'src/interfaces/album.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { JobStatus } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; -import { IPersonRepository } from 'src/interfaces/person.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; @@ -18,65 +14,30 @@ import { StorageTemplateService } from 'src/services/storage-template.service'; import { albumStub } from 'test/fixtures/album.stub'; import { assetStub } from 'test/fixtures/asset.stub'; import { userStub } from 'test/fixtures/user.stub'; -import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock'; -import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; -import { newConfigRepositoryMock } from 'test/repositories/config.repository.mock'; -import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; -import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; -import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; -import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock'; -import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; -import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; -import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock'; -import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; +import { newMockRepositories } from 'test/utils'; import { Mocked } from 'vitest'; describe(StorageTemplateService.name, () => { let sut: StorageTemplateService; let albumMock: Mocked; let assetMock: Mocked; - let configMock: Mocked; let cryptoMock: Mocked; - let databaseMock: Mocked; let moveMock: Mocked; - let personMock: Mocked; let storageMock: Mocked; let systemMock: Mocked; let userMock: Mocked; - let loggerMock: Mocked; it('should work', () => { expect(sut).toBeDefined(); }); beforeEach(() => { - assetMock = newAssetRepositoryMock(); - albumMock = newAlbumRepositoryMock(); - configMock = newConfigRepositoryMock(); - cryptoMock = newCryptoRepositoryMock(); - databaseMock = newDatabaseRepositoryMock(); - moveMock = newMoveRepositoryMock(); - personMock = newPersonRepositoryMock(); - storageMock = newStorageRepositoryMock(); - systemMock = newSystemMetadataRepositoryMock(); - userMock = newUserRepositoryMock(); - loggerMock = newLoggerRepositoryMock(); + const { deps, mocks } = newMockRepositories(); + ({ albumMock, assetMock, cryptoMock, moveMock, storageMock, systemMock, userMock } = mocks); systemMock.get.mockResolvedValue({ storageTemplate: { enabled: true } }); - sut = new StorageTemplateService( - albumMock, - assetMock, - configMock, - systemMock, - moveMock, - personMock, - storageMock, - userMock, - cryptoMock, - databaseMock, - loggerMock, - ); + sut = new StorageTemplateService(...deps); sut.onConfigUpdate({ newConfig: defaults }); }); diff --git a/server/src/services/storage-template.service.ts b/server/src/services/storage-template.service.ts index c0bf11b186a28e..e400981f541c5b 100644 --- a/server/src/services/storage-template.service.ts +++ b/server/src/services/storage-template.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import handlebar from 'handlebars'; import { DateTime } from 'luxon'; import path from 'node:path'; @@ -16,19 +16,9 @@ import { StorageCore } from 'src/cores/storage.core'; import { OnEvent } from 'src/decorators'; import { AssetEntity } from 'src/entities/asset.entity'; import { AssetPathType, AssetType, StorageFolder } from 'src/enum'; -import { IAlbumRepository } from 'src/interfaces/album.interface'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface'; +import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; import { IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IMoveRepository } from 'src/interfaces/move.interface'; -import { IPersonRepository } from 'src/interfaces/person.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; import { BaseService } from 'src/services/base.service'; import { getLivePhotoMotionFilename } from 'src/utils/file'; import { usePagination } from 'src/utils/pagination'; @@ -47,7 +37,6 @@ interface RenderMetadata { @Injectable() export class StorageTemplateService extends BaseService { - private storageCore: StorageCore; private _template: { compiled: HandlebarsTemplateDelegate; raw: string; @@ -61,33 +50,6 @@ export class StorageTemplateService extends BaseService { return this._template; } - constructor( - @Inject(IAlbumRepository) private albumRepository: IAlbumRepository, - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(IMoveRepository) moveRepository: IMoveRepository, - @Inject(IPersonRepository) personRepository: IPersonRepository, - @Inject(IStorageRepository) private storageRepository: IStorageRepository, - @Inject(IUserRepository) private userRepository: IUserRepository, - @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository, - @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(StorageTemplateService.name); - this.storageCore = StorageCore.create( - assetRepository, - configRepository, - cryptoRepository, - moveRepository, - personRepository, - storageRepository, - systemMetadataRepository, - this.logger, - ); - } - @OnEvent({ name: 'config.update', server: true }) onConfigUpdate({ newConfig }: ArgOf<'config.update'>) { const template = newConfig.storageTemplate.template; diff --git a/server/src/services/storage.service.spec.ts b/server/src/services/storage.service.spec.ts index e0717df66860e5..0d3411f33d9a1e 100644 --- a/server/src/services/storage.service.spec.ts +++ b/server/src/services/storage.service.spec.ts @@ -1,33 +1,22 @@ import { SystemMetadataKey } from 'src/enum'; import { IConfigRepository } from 'src/interfaces/config.interface'; -import { IDatabaseRepository } from 'src/interfaces/database.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { StorageService } from 'src/services/storage.service'; -import { mockEnvData, newConfigRepositoryMock } from 'test/repositories/config.repository.mock'; -import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; -import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; -import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; -import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock'; +import { mockEnvData } from 'test/repositories/config.repository.mock'; +import { newMockRepositories } from 'test/utils'; import { Mocked } from 'vitest'; describe(StorageService.name, () => { let sut: StorageService; let configMock: Mocked; - let databaseMock: Mocked; let storageMock: Mocked; - let loggerMock: Mocked; let systemMock: Mocked; beforeEach(() => { - configMock = newConfigRepositoryMock(); - databaseMock = newDatabaseRepositoryMock(); - storageMock = newStorageRepositoryMock(); - loggerMock = newLoggerRepositoryMock(); - systemMock = newSystemMetadataRepositoryMock(); - - sut = new StorageService(configMock, databaseMock, storageMock, loggerMock, systemMock); + const { deps, mocks } = newMockRepositories(); + ({ configMock, storageMock, systemMock } = mocks); + sut = new StorageService(...deps); }); it('should work', () => { diff --git a/server/src/services/storage.service.ts b/server/src/services/storage.service.ts index e2c1ef28e20ed1..15868d646d4df2 100644 --- a/server/src/services/storage.service.ts +++ b/server/src/services/storage.service.ts @@ -1,36 +1,23 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { join } from 'node:path'; import { StorageCore } from 'src/cores/storage.core'; import { OnEvent } from 'src/decorators'; import { StorageFolder, SystemMetadataKey } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface'; +import { DatabaseLock } from 'src/interfaces/database.interface'; import { IDeleteFilesJob, JobStatus } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { BaseService } from 'src/services/base.service'; import { ImmichStartupError } from 'src/utils/events'; const docsMessage = `Please see https://immich.app/docs/administration/system-integrity#folder-checks for more information.`; @Injectable() -export class StorageService { - constructor( - @Inject(IConfigRepository) private configRepository: IConfigRepository, - @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository, - @Inject(IStorageRepository) private storageRepository: IStorageRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, - @Inject(ISystemMetadataRepository) private systemMetadata: ISystemMetadataRepository, - ) { - this.logger.setContext(StorageService.name); - } - +export class StorageService extends BaseService { @OnEvent({ name: 'app.bootstrap' }) async onBootstrap() { const envData = this.configRepository.getEnv(); await this.databaseRepository.withLock(DatabaseLock.SystemFileMounts, async () => { - const flags = (await this.systemMetadata.get(SystemMetadataKey.SYSTEM_FLAGS)) || { mountFiles: false }; + const flags = (await this.systemMetadataRepository.get(SystemMetadataKey.SYSTEM_FLAGS)) || { mountFiles: false }; const enabled = flags.mountFiles ?? false; this.logger.log(`Verifying system mount folder checks (enabled=${enabled})`); @@ -49,7 +36,7 @@ export class StorageService { if (!flags.mountFiles) { flags.mountFiles = true; - await this.systemMetadata.set(SystemMetadataKey.SYSTEM_FLAGS, flags); + await this.systemMetadataRepository.set(SystemMetadataKey.SYSTEM_FLAGS, flags); this.logger.log('Successfully enabled system mount folders checks'); } diff --git a/server/src/services/sync.service.ts b/server/src/services/sync.service.ts index 7da3fbd9be58db..664e978c3d3c0f 100644 --- a/server/src/services/sync.service.ts +++ b/server/src/services/sync.service.ts @@ -1,28 +1,17 @@ -import { Inject } from '@nestjs/common'; import { DateTime } from 'luxon'; import { AUDIT_LOG_MAX_DURATION } from 'src/constants'; import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { AssetDeltaSyncDto, AssetDeltaSyncResponseDto, AssetFullSyncDto } from 'src/dtos/sync.dto'; import { DatabaseAction, EntityType, Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IAuditRepository } from 'src/interfaces/audit.interface'; -import { IPartnerRepository } from 'src/interfaces/partner.interface'; +import { BaseService } from 'src/services/base.service'; import { requireAccess } from 'src/utils/access'; import { getMyPartnerIds } from 'src/utils/asset.util'; import { setIsEqual } from 'src/utils/set'; const FULL_SYNC = { needsFullSync: true, deleted: [], upserted: [] }; -export class SyncService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository, - @Inject(IAuditRepository) private auditRepository: IAuditRepository, - ) {} - +export class SyncService extends BaseService { async getFullSync(auth: AuthDto, dto: AssetFullSyncDto): Promise { // mobile implementation is faster if this is a single id const userId = dto.userId || auth.user.id; diff --git a/server/src/services/system-config.service.ts b/server/src/services/system-config.service.ts index ff749f1105546a..96a1f0897bb361 100644 --- a/server/src/services/system-config.service.ts +++ b/server/src/services/system-config.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { instanceToPlain } from 'class-transformer'; import _ from 'lodash'; import { defaults } from 'src/config'; @@ -14,26 +14,13 @@ import { } from 'src/constants'; import { OnEvent } from 'src/decorators'; import { SystemConfigDto, SystemConfigTemplateStorageOptionDto, mapConfig } from 'src/dtos/system-config.dto'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ArgOf, IEventRepository } from 'src/interfaces/event.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { ArgOf } from 'src/interfaces/event.interface'; import { BaseService } from 'src/services/base.service'; import { clearConfigCache } from 'src/utils/config'; import { toPlainObject } from 'src/utils/object'; @Injectable() export class SystemConfigService extends BaseService { - constructor( - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(SystemConfigService.name); - } - @OnEvent({ name: 'app.bootstrap', priority: -100 }) async onBootstrap() { const config = await this.getConfig({ withCache: false }); diff --git a/server/src/services/system-metadata.service.ts b/server/src/services/system-metadata.service.ts index c2c9a4fdfc8c20..93449c7a7b5d7c 100644 --- a/server/src/services/system-metadata.service.ts +++ b/server/src/services/system-metadata.service.ts @@ -1,29 +1,27 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { AdminOnboardingResponseDto, AdminOnboardingUpdateDto, ReverseGeocodingStateResponseDto, } from 'src/dtos/system-metadata.dto'; import { SystemMetadataKey } from 'src/enum'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { BaseService } from 'src/services/base.service'; @Injectable() -export class SystemMetadataService { - constructor(@Inject(ISystemMetadataRepository) private repository: ISystemMetadataRepository) {} - +export class SystemMetadataService extends BaseService { async getAdminOnboarding(): Promise { - const value = await this.repository.get(SystemMetadataKey.ADMIN_ONBOARDING); + const value = await this.systemMetadataRepository.get(SystemMetadataKey.ADMIN_ONBOARDING); return { isOnboarded: false, ...value }; } async updateAdminOnboarding(dto: AdminOnboardingUpdateDto): Promise { - await this.repository.set(SystemMetadataKey.ADMIN_ONBOARDING, { + await this.systemMetadataRepository.set(SystemMetadataKey.ADMIN_ONBOARDING, { isOnboarded: dto.isOnboarded, }); } async getReverseGeocodingState(): Promise { - const value = await this.repository.get(SystemMetadataKey.REVERSE_GEOCODING_STATE); + const value = await this.systemMetadataRepository.get(SystemMetadataKey.REVERSE_GEOCODING_STATE); return { lastUpdate: null, lastImportFileName: null, ...value }; } } diff --git a/server/src/services/tag.service.ts b/server/src/services/tag.service.ts index cc6d64f749d203..e01894cb47b408 100644 --- a/server/src/services/tag.service.ts +++ b/server/src/services/tag.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { @@ -12,24 +12,17 @@ import { } from 'src/dtos/tag.dto'; import { TagEntity } from 'src/entities/tag.entity'; import { Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IEventRepository } from 'src/interfaces/event.interface'; import { JobStatus } from 'src/interfaces/job.interface'; -import { AssetTagItem, ITagRepository } from 'src/interfaces/tag.interface'; +import { AssetTagItem } from 'src/interfaces/tag.interface'; +import { BaseService } from 'src/services/base.service'; import { checkAccess, requireAccess } from 'src/utils/access'; import { addAssets, removeAssets } from 'src/utils/asset.util'; import { upsertTags } from 'src/utils/tag'; @Injectable() -export class TagService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(ITagRepository) private repository: ITagRepository, - ) {} - +export class TagService extends BaseService { async getAll(auth: AuthDto) { - const tags = await this.repository.getAll(auth.user.id); + const tags = await this.tagRepository.getAll(auth.user.id); return tags.map((tag) => mapTag(tag)); } @@ -43,7 +36,7 @@ export class TagService { let parent: TagEntity | undefined; if (dto.parentId) { await requireAccess(this.access, { auth, permission: Permission.TAG_READ, ids: [dto.parentId] }); - parent = (await this.repository.get(dto.parentId)) || undefined; + parent = (await this.tagRepository.get(dto.parentId)) || undefined; if (!parent) { throw new BadRequestException('Tag not found'); } @@ -51,12 +44,12 @@ export class TagService { const userId = auth.user.id; const value = parent ? `${parent.value}/${dto.name}` : dto.name; - const duplicate = await this.repository.getByValue(userId, value); + const duplicate = await this.tagRepository.getByValue(userId, value); if (duplicate) { throw new BadRequestException(`A tag with that name already exists`); } - const tag = await this.repository.create({ userId, value, parent }); + const tag = await this.tagRepository.create({ userId, value, parent }); return mapTag(tag); } @@ -65,12 +58,12 @@ export class TagService { await requireAccess(this.access, { auth, permission: Permission.TAG_UPDATE, ids: [id] }); const { color } = dto; - const tag = await this.repository.update({ id, color }); + const tag = await this.tagRepository.update({ id, color }); return mapTag(tag); } async upsert(auth: AuthDto, dto: TagUpsertDto) { - const tags = await upsertTags(this.repository, { userId: auth.user.id, tags: dto.tags }); + const tags = await upsertTags(this.tagRepository, { userId: auth.user.id, tags: dto.tags }); return tags.map((tag) => mapTag(tag)); } @@ -79,7 +72,7 @@ export class TagService { // TODO sync tag changes for affected assets - await this.repository.delete(id); + await this.tagRepository.delete(id); } async bulkTagAssets(auth: AuthDto, dto: TagBulkAssetsDto): Promise { @@ -95,7 +88,7 @@ export class TagService { } } - const results = await this.repository.upsertAssetIds(items); + const results = await this.tagRepository.upsertAssetIds(items); for (const assetId of new Set(results.map((item) => item.assetId))) { await this.eventRepository.emit('asset.tag', { assetId }); } @@ -108,7 +101,7 @@ export class TagService { const results = await addAssets( auth, - { access: this.access, bulk: this.repository }, + { access: this.access, bulk: this.tagRepository }, { parentId: id, assetIds: dto.ids }, ); @@ -126,7 +119,7 @@ export class TagService { const results = await removeAssets( auth, - { access: this.access, bulk: this.repository }, + { access: this.access, bulk: this.tagRepository }, { parentId: id, assetIds: dto.ids, canAlwaysRemove: Permission.TAG_DELETE }, ); @@ -140,12 +133,12 @@ export class TagService { } async handleTagCleanup() { - await this.repository.deleteEmptyTags(); + await this.tagRepository.deleteEmptyTags(); return JobStatus.SUCCESS; } private async findOrFail(id: string) { - const tag = await this.repository.get(id); + const tag = await this.tagRepository.get(id); if (!tag) { throw new BadRequestException('Tag not found'); } diff --git a/server/src/services/timeline.service.ts b/server/src/services/timeline.service.ts index bc08505b944ebb..05c5018bfbbbcc 100644 --- a/server/src/services/timeline.service.ts +++ b/server/src/services/timeline.service.ts @@ -1,26 +1,18 @@ -import { BadRequestException, Inject } from '@nestjs/common'; +import { BadRequestException } from '@nestjs/common'; import { AssetResponseDto, SanitizedAssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { TimeBucketAssetDto, TimeBucketDto, TimeBucketResponseDto } from 'src/dtos/time-bucket.dto'; import { Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IAssetRepository, TimeBucketOptions } from 'src/interfaces/asset.interface'; -import { IPartnerRepository } from 'src/interfaces/partner.interface'; +import { TimeBucketOptions } from 'src/interfaces/asset.interface'; +import { BaseService } from 'src/services/base.service'; import { requireAccess } from 'src/utils/access'; import { getMyPartnerIds } from 'src/utils/asset.util'; -export class TimelineService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IAssetRepository) private repository: IAssetRepository, - @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository, - ) {} - +export class TimelineService extends BaseService { async getTimeBuckets(auth: AuthDto, dto: TimeBucketDto): Promise { await this.timeBucketChecks(auth, dto); const timeBucketOptions = await this.buildTimeBucketOptions(auth, dto); - - return this.repository.getTimeBuckets(timeBucketOptions); + return this.assetRepository.getTimeBuckets(timeBucketOptions); } async getTimeBucket( @@ -29,7 +21,7 @@ export class TimelineService { ): Promise { await this.timeBucketChecks(auth, dto); const timeBucketOptions = await this.buildTimeBucketOptions(auth, dto); - const assets = await this.repository.getTimeBucket(dto.timeBucket, timeBucketOptions); + const assets = await this.assetRepository.getTimeBucket(dto.timeBucket, timeBucketOptions); return !auth.sharedLink || auth.sharedLink?.showExif ? assets.map((asset) => mapAsset(asset, { withStack: true, auth })) : assets.map((asset) => mapAsset(asset, { stripMetadata: true, auth })); diff --git a/server/src/services/trash.service.ts b/server/src/services/trash.service.ts index 51771d38a2aa9e..779a86ecd7b5ff 100644 --- a/server/src/services/trash.service.ts +++ b/server/src/services/trash.service.ts @@ -1,28 +1,14 @@ -import { Inject } from '@nestjs/common'; import { OnEvent } from 'src/decorators'; import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { TrashResponseDto } from 'src/dtos/trash.dto'; import { Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IEventRepository } from 'src/interfaces/event.interface'; -import { IJobRepository, JOBS_ASSET_PAGINATION_SIZE, JobName, JobStatus } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { ITrashRepository } from 'src/interfaces/trash.interface'; +import { JOBS_ASSET_PAGINATION_SIZE, JobName, JobStatus } from 'src/interfaces/job.interface'; +import { BaseService } from 'src/services/base.service'; import { requireAccess } from 'src/utils/access'; import { usePagination } from 'src/utils/pagination'; -export class TrashService { - constructor( - @Inject(IAccessRepository) private access: IAccessRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(ITrashRepository) private trashRepository: ITrashRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, - ) { - this.logger.setContext(TrashService.name); - } - +export class TrashService extends BaseService { async restoreAssets(auth: AuthDto, dto: BulkIdsDto): Promise { const { ids } = dto; if (ids.length === 0) { diff --git a/server/src/services/user-admin.service.ts b/server/src/services/user-admin.service.ts index 75dff32f160a94..140d483f18b5db 100644 --- a/server/src/services/user-admin.service.ts +++ b/server/src/services/user-admin.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, ForbiddenException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common'; import { SALT_ROUNDS } from 'src/constants'; import { AuthDto } from 'src/dtos/auth.dto'; import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto'; @@ -11,28 +11,14 @@ import { mapUserAdmin, } from 'src/dtos/user.dto'; import { UserMetadataKey, UserStatus } from 'src/enum'; -import { IAlbumRepository } from 'src/interfaces/album.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { IEventRepository } from 'src/interfaces/event.interface'; -import { IJobRepository, JobName } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface'; +import { JobName } from 'src/interfaces/job.interface'; +import { UserFindOptions } from 'src/interfaces/user.interface'; +import { BaseService } from 'src/services/base.service'; import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences'; import { createUser } from 'src/utils/user'; @Injectable() -export class UserAdminService { - constructor( - @Inject(IAlbumRepository) private albumRepository: IAlbumRepository, - @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(IUserRepository) private userRepository: IUserRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, - ) { - this.logger.setContext(UserAdminService.name); - } - +export class UserAdminService extends BaseService { async search(auth: AuthDto, dto: UserAdminSearchDto): Promise { const users = await this.userRepository.getList({ withDeleted: dto.withDeleted }); return users.map((user) => mapUserAdmin(user)); @@ -40,7 +26,7 @@ export class UserAdminService { async create(dto: UserAdminCreateDto): Promise { const { notify, ...rest } = dto; - const user = await createUser({ userRepo: this.userRepository, cryptoRepo: this.cryptoRepository }, rest); + const user = await createUser({ userRepo: this.userRepository, cryptoRepo: this.crypto }, rest); await this.eventRepository.emit('user.signup', { notify: !!notify, @@ -78,7 +64,7 @@ export class UserAdminService { } if (dto.password) { - dto.password = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS); + dto.password = await this.crypto.hashBcrypt(dto.password, SALT_ROUNDS); } if (dto.storageLabel === '') { diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index 92c9c299944948..751c311b1643db 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { DateTime } from 'luxon'; import { getClientLicensePublicKey, getServerLicensePublicKey } from 'src/config'; import { SALT_ROUNDS } from 'src/constants'; @@ -11,34 +11,14 @@ import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto, mapUser, mapUse import { UserMetadataEntity } from 'src/entities/user-metadata.entity'; import { UserEntity } from 'src/entities/user.entity'; import { CacheControl, StorageFolder, UserMetadataKey } from 'src/enum'; -import { IAlbumRepository } from 'src/interfaces/album.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { IEntityJob, IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface'; +import { IEntityJob, JobName, JobStatus } from 'src/interfaces/job.interface'; +import { UserFindOptions } from 'src/interfaces/user.interface'; import { BaseService } from 'src/services/base.service'; import { ImmichFileResponse } from 'src/utils/file'; import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences'; @Injectable() export class UserService extends BaseService { - constructor( - @Inject(IAlbumRepository) private albumRepository: IAlbumRepository, - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(IStorageRepository) private storageRepository: IStorageRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(IUserRepository) private userRepository: IUserRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(UserService.name); - } - async search(): Promise { const users = await this.userRepository.getList({ withDeleted: false }); return users.map((user) => mapUser(user)); @@ -62,7 +42,7 @@ export class UserService extends BaseService { }; if (dto.password) { - const hashedPassword = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS); + const hashedPassword = await this.crypto.hashBcrypt(dto.password, SALT_ROUNDS); update.password = hashedPassword; update.shouldChangePassword = false; } @@ -153,13 +133,13 @@ export class UserService extends BaseService { throw new BadRequestException('Invalid license key'); } - const clientLicenseValid = this.cryptoRepository.verifySha256( + const clientLicenseValid = this.crypto.verifySha256( license.licenseKey, license.activationKey, getClientLicensePublicKey(), ); - const serverLicenseValid = this.cryptoRepository.verifySha256( + const serverLicenseValid = this.crypto.verifySha256( license.licenseKey, license.activationKey, getServerLicensePublicKey(), diff --git a/server/src/services/version.service.spec.ts b/server/src/services/version.service.spec.ts index ebc5d4b2322f8f..ce602c460ff2bb 100644 --- a/server/src/services/version.service.spec.ts +++ b/server/src/services/version.service.spec.ts @@ -2,7 +2,6 @@ import { DateTime } from 'luxon'; import { serverVersion } from 'src/constants'; import { ImmichEnvironment, SystemMetadataKey } from 'src/enum'; import { IConfigRepository } from 'src/interfaces/config.interface'; -import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; @@ -10,14 +9,8 @@ import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface'; import { VersionService } from 'src/services/version.service'; -import { mockEnvData, newConfigRepositoryMock } from 'test/repositories/config.repository.mock'; -import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; -import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; -import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; -import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; -import { newServerInfoRepositoryMock } from 'test/repositories/server-info.repository.mock'; -import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock'; -import { newVersionHistoryRepositoryMock } from 'test/repositories/version-history.repository.mock'; +import { mockEnvData } from 'test/repositories/config.repository.mock'; +import { newMockRepositories } from 'test/utils'; import { Mocked } from 'vitest'; const mockRelease = (version: string) => ({ @@ -33,34 +26,18 @@ const mockRelease = (version: string) => ({ describe(VersionService.name, () => { let sut: VersionService; let configMock: Mocked; - let databaseMock: Mocked; let eventMock: Mocked; let jobMock: Mocked; - let serverMock: Mocked; + let serverInfoMock: Mocked; let systemMock: Mocked; - let versionMock: Mocked; + let versionHistoryMock: Mocked; let loggerMock: Mocked; beforeEach(() => { - configMock = newConfigRepositoryMock(); - databaseMock = newDatabaseRepositoryMock(); - eventMock = newEventRepositoryMock(); - jobMock = newJobRepositoryMock(); - serverMock = newServerInfoRepositoryMock(); - systemMock = newSystemMetadataRepositoryMock(); - versionMock = newVersionHistoryRepositoryMock(); - loggerMock = newLoggerRepositoryMock(); - - sut = new VersionService( - configMock, - databaseMock, - eventMock, - jobMock, - serverMock, - systemMock, - versionMock, - loggerMock, - ); + const { deps, mocks } = newMockRepositories(); + ({ configMock, eventMock, jobMock, serverInfoMock, systemMock, versionHistoryMock, loggerMock } = mocks); + + sut = new VersionService(...deps); }); it('should work', () => { @@ -70,17 +47,17 @@ describe(VersionService.name, () => { describe('onBootstrap', () => { it('should record a new version', async () => { await expect(sut.onBootstrap()).resolves.toBeUndefined(); - expect(versionMock.create).toHaveBeenCalledWith({ version: expect.any(String) }); + expect(versionHistoryMock.create).toHaveBeenCalledWith({ version: expect.any(String) }); }); it('should skip a duplicate version', async () => { - versionMock.getLatest.mockResolvedValue({ + versionHistoryMock.getLatest.mockResolvedValue({ id: 'version-1', createdAt: new Date(), version: serverVersion.toString(), }); await expect(sut.onBootstrap()).resolves.toBeUndefined(); - expect(versionMock.create).not.toHaveBeenCalled(); + expect(versionHistoryMock.create).not.toHaveBeenCalled(); }); }); @@ -97,7 +74,7 @@ describe(VersionService.name, () => { describe('getVersionHistory', () => { it('should respond the server version history', async () => { const upgrade = { id: 'upgrade-1', createdAt: new Date(), version: '1.0.0' }; - versionMock.getAll.mockResolvedValue([upgrade]); + versionHistoryMock.getAll.mockResolvedValue([upgrade]); await expect(sut.getVersionHistory()).resolves.toEqual([upgrade]); }); }); @@ -128,7 +105,7 @@ describe(VersionService.name, () => { }); it('should run if it has been > 60 minutes', async () => { - serverMock.getGitHubRelease.mockResolvedValue(mockRelease('v100.0.0')); + serverInfoMock.getGitHubRelease.mockResolvedValue(mockRelease('v100.0.0')); systemMock.get.mockResolvedValue({ checkedAt: DateTime.utc().minus({ minutes: 65 }).toISO(), releaseVersion: '1.0.0', @@ -140,7 +117,7 @@ describe(VersionService.name, () => { }); it('should not notify if the version is equal', async () => { - serverMock.getGitHubRelease.mockResolvedValue(mockRelease(serverVersion.toString())); + serverInfoMock.getGitHubRelease.mockResolvedValue(mockRelease(serverVersion.toString())); await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.SUCCESS); expect(systemMock.set).toHaveBeenCalledWith(SystemMetadataKey.VERSION_CHECK_STATE, { checkedAt: expect.any(String), @@ -150,7 +127,7 @@ describe(VersionService.name, () => { }); it('should handle a github error', async () => { - serverMock.getGitHubRelease.mockRejectedValue(new Error('GitHub is down')); + serverInfoMock.getGitHubRelease.mockRejectedValue(new Error('GitHub is down')); await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.FAILED); expect(systemMock.set).not.toHaveBeenCalled(); expect(eventMock.clientBroadcast).not.toHaveBeenCalled(); diff --git a/server/src/services/version.service.ts b/server/src/services/version.service.ts index 60ea388e5daf10..231ced1a950af6 100644 --- a/server/src/services/version.service.ts +++ b/server/src/services/version.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; import semver, { SemVer } from 'semver'; import { serverVersion } from 'src/constants'; @@ -6,14 +6,9 @@ import { OnEvent } from 'src/decorators'; import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; import { VersionCheckMetadata } from 'src/entities/system-metadata.entity'; import { ImmichEnvironment, SystemMetadataKey } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface'; -import { ArgOf, IEventRepository } from 'src/interfaces/event.interface'; -import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface'; +import { DatabaseLock } from 'src/interfaces/database.interface'; +import { ArgOf } from 'src/interfaces/event.interface'; +import { JobName, JobStatus } from 'src/interfaces/job.interface'; import { BaseService } from 'src/services/base.service'; const asNotification = ({ checkedAt, releaseVersion }: VersionCheckMetadata): ReleaseNotification => { @@ -27,20 +22,6 @@ const asNotification = ({ checkedAt, releaseVersion }: VersionCheckMetadata): Re @Injectable() export class VersionService extends BaseService { - constructor( - @Inject(IConfigRepository) configRepository: IConfigRepository, - @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository, - @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(IServerInfoRepository) private repository: IServerInfoRepository, - @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository, - @Inject(IVersionHistoryRepository) private versionRepository: IVersionHistoryRepository, - @Inject(ILoggerRepository) logger: ILoggerRepository, - ) { - super(configRepository, systemMetadataRepository, logger); - this.logger.setContext(VersionService.name); - } - @OnEvent({ name: 'app.bootstrap' }) async onBootstrap(): Promise { await this.handleVersionCheck(); @@ -91,7 +72,8 @@ export class VersionService extends BaseService { } } - const { tag_name: releaseVersion, published_at: publishedAt } = await this.repository.getGitHubRelease(); + const { tag_name: releaseVersion, published_at: publishedAt } = + await this.serverInfoRepository.getGitHubRelease(); const metadata: VersionCheckMetadata = { checkedAt: DateTime.utc().toISO(), releaseVersion }; await this.systemMetadataRepository.set(SystemMetadataKey.VERSION_CHECK_STATE, metadata); diff --git a/server/src/services/view.service.spec.ts b/server/src/services/view.service.spec.ts index 8d17e4d8974014..b73bfad5c02a18 100644 --- a/server/src/services/view.service.spec.ts +++ b/server/src/services/view.service.spec.ts @@ -3,7 +3,7 @@ import { IViewRepository } from 'src/interfaces/view.interface'; import { ViewService } from 'src/services/view.service'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; -import { newViewRepositoryMock } from 'test/repositories/view.repository.mock'; +import { newMockRepositories } from 'test/utils'; import { Mocked } from 'vitest'; @@ -12,9 +12,10 @@ describe(ViewService.name, () => { let viewMock: Mocked; beforeEach(() => { - viewMock = newViewRepositoryMock(); + const { deps, mocks } = newMockRepositories(); + ({ viewMock } = mocks); - sut = new ViewService(viewMock); + sut = new ViewService(...deps); }); it('should work', () => { diff --git a/server/src/services/view.service.ts b/server/src/services/view.service.ts index d870f9fd2e1a68..cb805368705df8 100644 --- a/server/src/services/view.service.ts +++ b/server/src/services/view.service.ts @@ -1,11 +1,8 @@ -import { Inject } from '@nestjs/common'; import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { IViewRepository } from 'src/interfaces/view.interface'; - -export class ViewService { - constructor(@Inject(IViewRepository) private viewRepository: IViewRepository) {} +import { BaseService } from 'src/services/base.service'; +export class ViewService extends BaseService { getUniqueOriginalPaths(auth: AuthDto): Promise { return this.viewRepository.getUniqueOriginalPaths(auth.user.id); } diff --git a/server/test/utils.ts b/server/test/utils.ts new file mode 100644 index 00000000000000..23586d52109c8a --- /dev/null +++ b/server/test/utils.ts @@ -0,0 +1,158 @@ +import { BaseService } from 'src/services/base.service'; +import { newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; +import { newActivityRepositoryMock } from 'test/repositories/activity.repository.mock'; +import { newAlbumUserRepositoryMock } from 'test/repositories/album-user.repository.mock'; +import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock'; +import { newKeyRepositoryMock } from 'test/repositories/api-key.repository.mock'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newAuditRepositoryMock } from 'test/repositories/audit.repository.mock'; +import { newConfigRepositoryMock } from 'test/repositories/config.repository.mock'; +import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; +import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; +import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; +import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; +import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock'; +import { newMapRepositoryMock } from 'test/repositories/map.repository.mock'; +import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock'; +import { newMemoryRepositoryMock } from 'test/repositories/memory.repository.mock'; +import { newMetadataRepositoryMock } from 'test/repositories/metadata.repository.mock'; +import { newMetricRepositoryMock } from 'test/repositories/metric.repository.mock'; +import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock'; +import { newNotificationRepositoryMock } from 'test/repositories/notification.repository.mock'; +import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; +import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; +import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock'; +import { newServerInfoRepositoryMock } from 'test/repositories/server-info.repository.mock'; +import { newSessionRepositoryMock } from 'test/repositories/session.repository.mock'; +import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock'; +import { newStackRepositoryMock } from 'test/repositories/stack.repository.mock'; +import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; +import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock'; +import { newTagRepositoryMock } from 'test/repositories/tag.repository.mock'; +import { newTrashRepositoryMock } from 'test/repositories/trash.repository.mock'; +import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; +import { newVersionHistoryRepositoryMock } from 'test/repositories/version-history.repository.mock'; +import { newViewRepositoryMock } from 'test/repositories/view.repository.mock'; + +export type ServiceDeps = ConstructorParameters; + +export const newMockRepositories = () => { + const accessMock = newAccessRepositoryMock(); + const loggerMock = newLoggerRepositoryMock(); + const cryptoMock = newCryptoRepositoryMock(); + const activityMock = newActivityRepositoryMock(); + const auditMock = newAuditRepositoryMock(); + const albumMock = newAlbumRepositoryMock(); + const albumUserMock = newAlbumUserRepositoryMock(); + const assetMock = newAssetRepositoryMock(); + const configMock = newConfigRepositoryMock(); + const databaseMock = newDatabaseRepositoryMock(); + const eventMock = newEventRepositoryMock(); + const jobMock = newJobRepositoryMock(); + const keyMock = newKeyRepositoryMock(); + const libraryMock = newLibraryRepositoryMock(); + const machineLearningMock = newMachineLearningRepositoryMock(); + const mapMock = newMapRepositoryMock(); + const mediaMock = newMediaRepositoryMock(); + const memoryMock = newMemoryRepositoryMock(); + const metadataMock = newMetadataRepositoryMock(); + const metricMock = newMetricRepositoryMock(); + const moveMock = newMoveRepositoryMock(); + const notificationMock = newNotificationRepositoryMock(); + const partnerMock = newPartnerRepositoryMock(); + const personMock = newPersonRepositoryMock(); + const searchMock = newSearchRepositoryMock(); + const serverInfoMock = newServerInfoRepositoryMock(); + const sessionMock = newSessionRepositoryMock(); + const sharedLinkMock = newSharedLinkRepositoryMock(); + const stackMock = newStackRepositoryMock(); + const storageMock = newStorageRepositoryMock(); + const systemMock = newSystemMetadataRepositoryMock(); + const tagMock = newTagRepositoryMock(); + const trashMock = newTrashRepositoryMock(); + const userMock = newUserRepositoryMock(); + const versionHistoryMock = newVersionHistoryRepositoryMock(); + const viewMock = newViewRepositoryMock(); + + const mocks = { + accessMock, + loggerMock, + cryptoMock, + activityMock, + auditMock, + albumMock, + albumUserMock, + assetMock, + configMock, + databaseMock, + eventMock, + jobMock, + keyMock, + libraryMock, + machineLearningMock, + mapMock, + mediaMock, + memoryMock, + metadataMock, + metricMock, + moveMock, + notificationMock, + partnerMock, + personMock, + searchMock, + serverInfoMock, + sessionMock, + sharedLinkMock, + stackMock, + storageMock, + systemMock, + tagMock, + trashMock, + userMock, + versionHistoryMock, + viewMock, + }; + + const deps: ServiceDeps = [ + accessMock, + loggerMock, + cryptoMock, + activityMock, + auditMock, + albumMock, + albumUserMock, + assetMock, + configMock, + databaseMock, + eventMock, + jobMock, + keyMock, + libraryMock, + machineLearningMock, + mapMock, + mediaMock, + memoryMock, + metadataMock, + metricMock, + moveMock, + notificationMock, + partnerMock, + personMock, + searchMock, + serverInfoMock, + sessionMock, + sharedLinkMock, + stackMock, + storageMock, + systemMock, + tagMock, + trashMock, + userMock, + versionHistoryMock, + viewMock, + ]; + + return { deps, mocks }; +};