diff --git a/server/src/cores/storage.core.spec.ts b/server/src/cores/storage.core.spec.ts index a663673306..7bb2cdb1be 100644 --- a/server/src/cores/storage.core.spec.ts +++ b/server/src/cores/storage.core.spec.ts @@ -5,6 +5,7 @@ vitest.mock('src/constants', () => ({ APP_MEDIA_LOCATION: '/photos', ADDED_IN_PREFIX: 'This property was added in ', DEPRECATED_IN_PREFIX: 'This property was deprecated in ', + IWorker: 'IWorker', })); describe('StorageCore', () => { diff --git a/server/src/cores/storage.core.ts b/server/src/cores/storage.core.ts index 86f7be9ffd..3160331dd4 100644 --- a/server/src/cores/storage.core.ts +++ b/server/src/cores/storage.core.ts @@ -4,13 +4,13 @@ import { APP_MEDIA_LOCATION } from 'src/constants'; import { AssetEntity } from 'src/entities/asset.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { AssetFileType, AssetPathType, ImageFormat, PathType, PersonPathType, StorageFolder } from 'src/enum'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IMoveRepository } from 'src/interfaces/move.interface'; -import { IPersonRepository } from 'src/interfaces/person.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; +import { AssetRepository } from 'src/repositories/asset.repository'; import { ConfigRepository } from 'src/repositories/config.repository'; import { CryptoRepository } from 'src/repositories/crypto.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; +import { MoveRepository } from 'src/repositories/move.repository'; +import { PersonRepository } from 'src/repositories/person.repository'; +import { StorageRepository } from 'src/repositories/storage.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; import { getAssetFiles } from 'src/utils/asset.util'; import { getConfig } from 'src/utils/config'; @@ -33,23 +33,23 @@ let instance: StorageCore | null; export class StorageCore { private constructor( - private assetRepository: IAssetRepository, + private assetRepository: AssetRepository, private configRepository: ConfigRepository, private cryptoRepository: CryptoRepository, - private moveRepository: IMoveRepository, - private personRepository: IPersonRepository, - private storageRepository: IStorageRepository, + private moveRepository: MoveRepository, + private personRepository: PersonRepository, + private storageRepository: StorageRepository, private systemMetadataRepository: SystemMetadataRepository, private logger: LoggingRepository, ) {} static create( - assetRepository: IAssetRepository, + assetRepository: AssetRepository, configRepository: ConfigRepository, cryptoRepository: CryptoRepository, - moveRepository: IMoveRepository, - personRepository: IPersonRepository, - storageRepository: IStorageRepository, + moveRepository: MoveRepository, + personRepository: PersonRepository, + storageRepository: StorageRepository, systemMetadataRepository: SystemMetadataRepository, logger: LoggingRepository, ) { diff --git a/server/src/dtos/asset.dto.ts b/server/src/dtos/asset.dto.ts index 8aa63f2f69..32b14055d5 100644 --- a/server/src/dtos/asset.dto.ts +++ b/server/src/dtos/asset.dto.ts @@ -15,7 +15,7 @@ import { } from 'class-validator'; import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AssetType } from 'src/enum'; -import { AssetStats } from 'src/interfaces/asset.interface'; +import { AssetStats } from 'src/repositories/asset.repository'; import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; export class DeviceIdDto { diff --git a/server/src/dtos/partner.dto.ts b/server/src/dtos/partner.dto.ts index 38573998d6..9d86415dc3 100644 --- a/server/src/dtos/partner.dto.ts +++ b/server/src/dtos/partner.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty } from 'class-validator'; import { UserResponseDto } from 'src/dtos/user.dto'; -import { PartnerDirection } from 'src/interfaces/partner.interface'; +import { PartnerDirection } from 'src/repositories/partner.repository'; export class UpdatePartnerDto { @IsNotEmpty() diff --git a/server/src/dtos/time-bucket.dto.ts b/server/src/dtos/time-bucket.dto.ts index dd7a01df35..a9dfa49a07 100644 --- a/server/src/dtos/time-bucket.dto.ts +++ b/server/src/dtos/time-bucket.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; import { AssetOrder } from 'src/enum'; -import { TimeBucketSize } from 'src/interfaces/asset.interface'; +import { TimeBucketSize } from 'src/repositories/asset.repository'; import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; export class TimeBucketDto { diff --git a/server/src/entities/asset.entity.ts b/server/src/entities/asset.entity.ts index 594dd17785..a90236de84 100644 --- a/server/src/entities/asset.entity.ts +++ b/server/src/entities/asset.entity.ts @@ -13,8 +13,8 @@ import { StackEntity } from 'src/entities/stack.entity'; import { TagEntity } from 'src/entities/tag.entity'; import { UserEntity } from 'src/entities/user.entity'; import { AssetFileType, AssetStatus, AssetType } from 'src/enum'; -import { TimeBucketSize } from 'src/interfaces/asset.interface'; -import { AssetSearchBuilderOptions } from 'src/interfaces/search.interface'; +import { TimeBucketSize } from 'src/repositories/asset.repository'; +import { AssetSearchBuilderOptions } from 'src/repositories/search.repository'; import { anyUuid, asUuid } from 'src/utils/database'; import { Column, diff --git a/server/src/enum.ts b/server/src/enum.ts index 3440d45cee..332ae50ee8 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -384,3 +384,10 @@ export enum ExifOrientation { MirrorHorizontalRotate90CW = 7, Rotate270CW = 8, } + +export enum DatabaseExtension { + CUBE = 'cube', + EARTH_DISTANCE = 'earthdistance', + VECTOR = 'vector', + VECTORS = 'vectors', +} diff --git a/server/src/interfaces/album.interface.ts b/server/src/interfaces/album.interface.ts deleted file mode 100644 index 36a6d8a1d2..0000000000 --- a/server/src/interfaces/album.interface.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Insertable, Updateable } from 'kysely'; -import { Albums } from 'src/db'; -import { AlbumUserCreateDto } from 'src/dtos/album.dto'; -import { AlbumEntity } from 'src/entities/album.entity'; -import { IBulkAsset } from 'src/utils/asset.util'; - -export const IAlbumRepository = 'IAlbumRepository'; - -export interface AlbumAssetCount { - albumId: string; - assetCount: number; - startDate: Date | null; - endDate: Date | null; -} - -export interface AlbumInfoOptions { - withAssets: boolean; -} - -export interface IAlbumRepository extends IBulkAsset { - getById(id: string, options: AlbumInfoOptions): Promise; - getByAssetId(ownerId: string, assetId: string): Promise; - removeAsset(assetId: string): Promise; - getMetadataForIds(ids: string[]): Promise; - getOwned(ownerId: string): Promise; - getShared(ownerId: string): Promise; - getNotShared(ownerId: string): Promise; - restoreAll(userId: string): Promise; - softDeleteAll(userId: string): Promise; - deleteAll(userId: string): Promise; - create(album: Insertable, assetIds: string[], albumUsers: AlbumUserCreateDto[]): Promise; - update(id: string, album: Updateable): Promise; - delete(id: string): Promise; - updateThumbnails(): Promise; -} diff --git a/server/src/interfaces/asset.interface.ts b/server/src/interfaces/asset.interface.ts deleted file mode 100644 index 5abaf9af26..0000000000 --- a/server/src/interfaces/asset.interface.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { Insertable, Updateable } from 'kysely'; -import { AssetFiles, AssetJobStatus, Assets, Exif } from 'src/db'; -import { AssetEntity } from 'src/entities/asset.entity'; -import { AssetFileType, AssetOrder, AssetStatus, AssetType } from 'src/enum'; -import { AssetSearchOptions, SearchExploreItem } from 'src/interfaces/search.interface'; -import { Paginated, PaginationOptions } from 'src/utils/pagination'; - -export type AssetStats = Record; - -export interface AssetStatsOptions { - isFavorite?: boolean; - isArchived?: boolean; - isTrashed?: boolean; -} - -export interface LivePhotoSearchOptions { - ownerId: string; - libraryId?: string | null; - livePhotoCID: string; - otherAssetId: string; - type: AssetType; -} - -export enum WithoutProperty { - THUMBNAIL = 'thumbnail', - ENCODED_VIDEO = 'encoded-video', - EXIF = 'exif', - SMART_SEARCH = 'smart-search', - DUPLICATE = 'duplicate', - FACES = 'faces', - SIDECAR = 'sidecar', -} - -export enum WithProperty { - SIDECAR = 'sidecar', -} - -export enum TimeBucketSize { - DAY = 'DAY', - MONTH = 'MONTH', -} - -export interface AssetBuilderOptions { - isArchived?: boolean; - isFavorite?: boolean; - isTrashed?: boolean; - isDuplicate?: boolean; - albumId?: string; - tagId?: string; - personId?: string; - userIds?: string[]; - withStacked?: boolean; - exifInfo?: boolean; - status?: AssetStatus; - assetType?: AssetType; -} - -export interface TimeBucketOptions extends AssetBuilderOptions { - size: TimeBucketSize; - order?: AssetOrder; -} - -export interface TimeBucketItem { - timeBucket: string; - count: number; -} - -export interface MonthDay { - day: number; - month: number; -} - -export interface AssetExploreFieldOptions { - maxFields: number; - minAssetsPerField: number; -} - -export interface AssetFullSyncOptions { - ownerId: string; - lastId?: string; - updatedUntil: Date; - limit: number; -} - -export interface AssetDeltaSyncOptions { - userIds: string[]; - updatedAfter: Date; - limit: number; -} - -export interface AssetUpdateDuplicateOptions { - targetDuplicateId: string | null; - assetIds: string[]; - duplicateIds: string[]; -} - -export interface UpsertFileOptions { - assetId: string; - type: AssetFileType; - path: string; -} - -export interface AssetGetByChecksumOptions { - ownerId: string; - checksum: Buffer; - libraryId?: string; -} - -export type AssetPathEntity = Pick; - -export interface GetByIdsRelations { - exifInfo?: boolean; - faces?: { person?: boolean }; - files?: boolean; - library?: boolean; - owner?: boolean; - smartSearch?: boolean; - stack?: { assets?: boolean }; - tags?: boolean; -} - -export interface DuplicateGroup { - duplicateId: string; - assets: AssetEntity[]; -} - -export interface DayOfYearAssets { - yearsAgo: number; - assets: AssetEntity[]; -} - -export const IAssetRepository = 'IAssetRepository'; - -export interface IAssetRepository { - create(asset: Insertable): Promise; - getByIds(ids: string[], relations?: GetByIdsRelations): Promise; - getByIdsWithAllRelations(ids: string[]): Promise; - getByDayOfYear(ownerIds: string[], monthDay: MonthDay): Promise; - getByChecksum(options: AssetGetByChecksumOptions): Promise; - getByChecksums(userId: string, checksums: Buffer[]): Promise; - getUploadAssetIdByChecksum(ownerId: string, checksum: Buffer): Promise; - getByAlbumId(pagination: PaginationOptions, albumId: string): Paginated; - getByDeviceIds(ownerId: string, deviceId: string, deviceAssetIds: string[]): Promise; - getByUserId(pagination: PaginationOptions, userId: string, options?: AssetSearchOptions): Paginated; - getById(id: string, relations?: GetByIdsRelations): Promise; - getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated; - getRandom(userIds: string[], count: number): Promise; - getLastUpdatedAssetForAlbumId(albumId: string): Promise; - getByLibraryIdAndOriginalPath(libraryId: string, originalPath: string): Promise; - deleteAll(ownerId: string): Promise; - getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated; - getAllByDeviceId(userId: string, deviceId: string): Promise; - getLivePhotoCount(motionId: string): Promise; - updateAll(ids: string[], options: Updateable): Promise; - updateDuplicates(options: AssetUpdateDuplicateOptions): Promise; - update(asset: Updateable & { id: string }): Promise; - remove(asset: AssetEntity): Promise; - findLivePhotoMatch(options: LivePhotoSearchOptions): Promise; - getStatistics(ownerId: string, options: AssetStatsOptions): Promise; - getTimeBuckets(options: TimeBucketOptions): Promise; - getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise; - upsertExif(exif: Insertable): Promise; - upsertJobStatus(...jobStatus: Insertable[]): Promise; - getAssetIdByCity(userId: string, options: AssetExploreFieldOptions): Promise>; - getDuplicates(userId: string): Promise; - getAllForUserFullSync(options: AssetFullSyncOptions): Promise; - getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise; - upsertFile(options: Insertable): Promise; - upsertFiles(options: Insertable[]): Promise; -} diff --git a/server/src/interfaces/crypto.interface.ts b/server/src/interfaces/crypto.interface.ts deleted file mode 100644 index c661695cf7..0000000000 --- a/server/src/interfaces/crypto.interface.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const ICryptoRepository = 'ICryptoRepository'; - -export interface ICryptoRepository { - randomBytes(size: number): Buffer; - randomUUID(): string; - hashFile(filePath: string | Buffer): Promise; - hashSha256(data: string): string; - verifySha256(data: string, encrypted: string, publicKey: string): boolean; - hashSha1(data: string | Buffer): Buffer; - hashBcrypt(data: string | Buffer, saltOrRounds: string | number): Promise; - compareBcrypt(data: string | Buffer, encrypted: string): boolean; - newPassword(bytes: number): string; -} diff --git a/server/src/interfaces/database.interface.ts b/server/src/interfaces/database.interface.ts deleted file mode 100644 index 8cfc040271..0000000000 --- a/server/src/interfaces/database.interface.ts +++ /dev/null @@ -1,78 +0,0 @@ -export enum DatabaseExtension { - CUBE = 'cube', - EARTH_DISTANCE = 'earthdistance', - VECTOR = 'vector', - VECTORS = 'vectors', -} - -export type VectorExtension = DatabaseExtension.VECTOR | DatabaseExtension.VECTORS; - -export type DatabaseConnectionURL = { - connectionType: 'url'; - url: string; -}; - -export type DatabaseConnectionParts = { - connectionType: 'parts'; - host: string; - port: number; - username: string; - password: string; - database: string; -}; - -export type DatabaseConnectionParams = DatabaseConnectionURL | DatabaseConnectionParts; - -export enum VectorIndex { - CLIP = 'clip_index', - FACE = 'face_index', -} - -export enum DatabaseLock { - GeodataImport = 100, - Migrations = 200, - SystemFileMounts = 300, - StorageTemplateMigration = 420, - VersionHistory = 500, - CLIPDimSize = 512, - Library = 1337, - GetSystemConfig = 69, - BackupDatabase = 42, -} - -export const EXTENSION_NAMES: Record = { - cube: 'cube', - earthdistance: 'earthdistance', - vector: 'pgvector', - vectors: 'pgvecto.rs', -} as const; - -export interface ExtensionVersion { - availableVersion: string | null; - installedVersion: string | null; -} - -export interface VectorUpdateResult { - restartRequired: boolean; -} - -export const IDatabaseRepository = 'IDatabaseRepository'; - -export interface IDatabaseRepository { - init(): void; - reconnect(): Promise; - shutdown(): Promise; - getExtensionVersion(extension: DatabaseExtension): Promise; - getExtensionVersionRange(extension: VectorExtension): string; - getPostgresVersion(): Promise; - getPostgresVersionRange(): string; - createExtension(extension: DatabaseExtension): Promise; - updateVectorExtension(extension: VectorExtension, version?: string): Promise; - reindex(index: VectorIndex): Promise; - shouldReindex(name: VectorIndex): Promise; - runMigrations(options?: { transaction?: 'all' | 'none' | 'each' }): Promise; - withLock(lock: DatabaseLock, callback: () => Promise): Promise; - tryLock(lock: DatabaseLock): Promise; - isBusy(lock: DatabaseLock): boolean; - wait(lock: DatabaseLock): Promise; -} diff --git a/server/src/interfaces/library.interface.ts b/server/src/interfaces/library.interface.ts deleted file mode 100644 index 66e9a7de29..0000000000 --- a/server/src/interfaces/library.interface.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Insertable, Updateable } from 'kysely'; -import { Libraries } from 'src/db'; -import { LibraryStatsResponseDto } from 'src/dtos/library.dto'; -import { LibraryEntity } from 'src/entities/library.entity'; - -export const ILibraryRepository = 'ILibraryRepository'; - -export interface ILibraryRepository { - getAll(withDeleted?: boolean): Promise; - getAllDeleted(): Promise; - get(id: string, withDeleted?: boolean): Promise; - create(library: Insertable): Promise; - delete(id: string): Promise; - softDelete(id: string): Promise; - update(id: string, library: Updateable): Promise; - getStatistics(id: string): Promise; -} diff --git a/server/src/interfaces/move.interface.ts b/server/src/interfaces/move.interface.ts deleted file mode 100644 index 4356d9df8c..0000000000 --- a/server/src/interfaces/move.interface.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Insertable, Updateable } from 'kysely'; -import { MoveHistory } from 'src/db'; -import { MoveEntity } from 'src/entities/move.entity'; -import { PathType } from 'src/enum'; - -export const IMoveRepository = 'IMoveRepository'; - -export type MoveCreate = Pick & Partial; - -export interface IMoveRepository { - create(entity: Insertable): Promise; - getByEntity(entityId: string, pathType: PathType): Promise; - update(id: string, entity: Updateable): Promise; - delete(id: string): Promise; -} diff --git a/server/src/interfaces/partner.interface.ts b/server/src/interfaces/partner.interface.ts deleted file mode 100644 index a6f50178ca..0000000000 --- a/server/src/interfaces/partner.interface.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Updateable } from 'kysely'; -import { Partners } from 'src/db'; -import { PartnerEntity } from 'src/entities/partner.entity'; - -export interface PartnerIds { - sharedById: string; - sharedWithId: string; -} - -export enum PartnerDirection { - SharedBy = 'shared-by', - SharedWith = 'shared-with', -} - -export const IPartnerRepository = 'IPartnerRepository'; - -export interface IPartnerRepository { - getAll(userId: string): Promise; - get(partner: PartnerIds): Promise; - create(partner: PartnerIds): Promise; - remove(partner: PartnerIds): Promise; - update(partner: PartnerIds, entity: Updateable): Promise; -} diff --git a/server/src/interfaces/person.interface.ts b/server/src/interfaces/person.interface.ts deleted file mode 100644 index 4719f047ec..0000000000 --- a/server/src/interfaces/person.interface.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Insertable, Selectable, Updateable } from 'kysely'; -import { AssetFaces, FaceSearch, Person } from 'src/db'; -import { AssetFaceEntity } from 'src/entities/asset-face.entity'; -import { PersonEntity } from 'src/entities/person.entity'; -import { SourceType } from 'src/enum'; -import { Paginated, PaginationOptions } from 'src/utils/pagination'; -import { FindOptionsRelations } from 'typeorm'; - -export const IPersonRepository = 'IPersonRepository'; - -export interface PersonSearchOptions { - minimumFaceCount: number; - withHidden: boolean; - closestFaceAssetId?: string; -} - -export interface PersonNameSearchOptions { - withHidden?: boolean; -} - -export interface PersonNameResponse { - id: string; - name: string; -} - -export interface AssetFaceId { - assetId: string; - personId: string; -} - -export interface UpdateFacesData { - oldPersonId?: string; - faceIds?: string[]; - newPersonId: string; -} - -export interface PersonStatistics { - assets: number; -} - -export interface PeopleStatistics { - total: number; - hidden: number; -} - -export interface DeleteFacesOptions { - sourceType: SourceType; -} - -export type UnassignFacesOptions = DeleteFacesOptions; - -export type SelectFaceOptions = (keyof Selectable)[]; - -export interface IPersonRepository { - getAll(options?: Partial): AsyncIterableIterator; - getAllForUser(pagination: PaginationOptions, userId: string, options: PersonSearchOptions): Paginated; - getAllWithoutFaces(): Promise; - getById(personId: string): Promise; - getByName(userId: string, personName: string, options: PersonNameSearchOptions): Promise; - getDistinctNames(userId: string, options: PersonNameSearchOptions): Promise; - - create(person: Insertable): Promise; - createAll(people: Insertable[]): Promise; - delete(entities: PersonEntity[]): Promise; - deleteFaces(options: DeleteFacesOptions): Promise; - refreshFaces( - facesToAdd: Insertable[], - faceIdsToRemove: string[], - embeddingsToAdd?: Insertable[], - ): Promise; - getAllFaces(options?: Partial): AsyncIterableIterator; - getFaceById(id: string): Promise; - getFaceByIdWithAssets( - id: string, - relations?: FindOptionsRelations, - select?: SelectFaceOptions, - ): Promise; - getFaces(assetId: string): Promise; - getFacesByIds(ids: AssetFaceId[]): Promise; - getRandomFace(personId: string): Promise; - getStatistics(personId: string): Promise; - reassignFace(assetFaceId: string, newPersonId: string): Promise; - getNumberOfPeople(userId: string): Promise; - reassignFaces(data: UpdateFacesData): Promise; - unassignFaces(options: UnassignFacesOptions): Promise; - update(person: Updateable & { id: string }): Promise; - updateAll(people: Insertable[]): Promise; - getLatestFaceDate(): Promise; -} diff --git a/server/src/interfaces/search.interface.ts b/server/src/interfaces/search.interface.ts deleted file mode 100644 index b9ae7b7194..0000000000 --- a/server/src/interfaces/search.interface.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { AssetEntity } from 'src/entities/asset.entity'; -import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity'; -import { AssetStatus, AssetType } from 'src/enum'; -import { Paginated } from 'src/utils/pagination'; - -export const ISearchRepository = 'ISearchRepository'; - -export interface SearchResult { - /** total matches */ - total: number; - /** collection size */ - count: number; - /** current page */ - page: number; - /** items for page */ - items: T[]; - /** score */ - distances: number[]; - facets: SearchFacet[]; -} - -export interface SearchFacet { - fieldName: string; - counts: Array<{ - count: number; - value: string; - }>; -} - -export type SearchExploreItemSet = Array<{ - value: string; - data: T; -}>; - -export interface SearchExploreItem { - fieldName: string; - items: SearchExploreItemSet; -} - -export interface SearchAssetIDOptions { - checksum?: Buffer; - deviceAssetId?: string; - id?: string; -} - -export interface SearchUserIdOptions { - deviceId?: string; - libraryId?: string | null; - userIds?: string[]; -} - -export type SearchIdOptions = SearchAssetIDOptions & SearchUserIdOptions; - -export interface SearchStatusOptions { - isArchived?: boolean; - isEncoded?: boolean; - isFavorite?: boolean; - isMotion?: boolean; - isOffline?: boolean; - isVisible?: boolean; - isNotInAlbum?: boolean; - type?: AssetType; - status?: AssetStatus; - withArchived?: boolean; - withDeleted?: boolean; -} - -export interface SearchOneToOneRelationOptions { - withExif?: boolean; - withStacked?: boolean; -} - -export interface SearchRelationOptions extends SearchOneToOneRelationOptions { - withFaces?: boolean; - withPeople?: boolean; -} - -export interface SearchDateOptions { - createdBefore?: Date; - createdAfter?: Date; - takenBefore?: Date; - takenAfter?: Date; - trashedBefore?: Date; - trashedAfter?: Date; - updatedBefore?: Date; - updatedAfter?: Date; -} - -export interface SearchPathOptions { - encodedVideoPath?: string; - originalFileName?: string; - originalPath?: string; - previewPath?: string; - thumbnailPath?: string; -} - -export interface SearchExifOptions { - city?: string | null; - country?: string | null; - lensModel?: string | null; - make?: string | null; - model?: string | null; - state?: string | null; - description?: string | null; -} - -export interface SearchEmbeddingOptions { - embedding: string; - userIds: string[]; -} - -export interface SearchPeopleOptions { - personIds?: string[]; -} - -export interface SearchTagOptions { - tagIds?: string[]; -} - -export interface SearchOrderOptions { - orderDirection?: 'asc' | 'desc'; -} - -export interface SearchPaginationOptions { - page: number; - size: number; -} - -type BaseAssetSearchOptions = SearchDateOptions & - SearchIdOptions & - SearchExifOptions & - SearchOrderOptions & - SearchPathOptions & - SearchStatusOptions & - SearchUserIdOptions & - SearchPeopleOptions & - SearchTagOptions; - -export type AssetSearchOptions = BaseAssetSearchOptions & SearchRelationOptions; - -export type AssetSearchOneToOneRelationOptions = BaseAssetSearchOptions & SearchOneToOneRelationOptions; - -export type AssetSearchBuilderOptions = Omit; - -export type SmartSearchOptions = SearchDateOptions & - SearchEmbeddingOptions & - SearchExifOptions & - SearchOneToOneRelationOptions & - SearchStatusOptions & - SearchUserIdOptions & - SearchPeopleOptions & - SearchTagOptions; - -export interface FaceEmbeddingSearch extends SearchEmbeddingOptions { - hasPerson?: boolean; - numResults: number; - maxDistance: number; -} - -export interface AssetDuplicateSearch { - assetId: string; - embedding: string; - maxDistance: number; - type: AssetType; - userIds: string[]; -} - -export interface FaceSearchResult { - distance: number; - id: string; - personId: string | null; -} - -export interface AssetDuplicateResult { - assetId: string; - duplicateId: string | null; - distance: number; -} - -export interface GetStatesOptions { - country?: string; -} - -export interface GetCitiesOptions extends GetStatesOptions { - state?: string; -} - -export interface GetCameraModelsOptions { - make?: string; -} - -export interface GetCameraMakesOptions { - model?: string; -} - -export interface ISearchRepository { - searchMetadata(pagination: SearchPaginationOptions, options: AssetSearchOptions): Paginated; - searchSmart(pagination: SearchPaginationOptions, options: SmartSearchOptions): Paginated; - searchDuplicates(options: AssetDuplicateSearch): Promise; - searchFaces(search: FaceEmbeddingSearch): Promise; - searchRandom(size: number, options: AssetSearchOptions): Promise; - upsert(assetId: string, embedding: string): Promise; - searchPlaces(placeName: string): Promise; - getAssetsByCity(userIds: string[]): Promise; - deleteAllSearchEmbeddings(): Promise; - getDimensionSize(): Promise; - setDimensionSize(dimSize: number): Promise; - getCountries(userIds: string[]): Promise>; - getStates(userIds: string[], options: GetStatesOptions): Promise>; - getCities(userIds: string[], options: GetCitiesOptions): Promise>; - getCameraMakes(userIds: string[], options: GetCameraMakesOptions): Promise>; - getCameraModels(userIds: string[], options: GetCameraModelsOptions): Promise>; -} diff --git a/server/src/interfaces/shared-link.interface.ts b/server/src/interfaces/shared-link.interface.ts deleted file mode 100644 index c030ceb736..0000000000 --- a/server/src/interfaces/shared-link.interface.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Insertable, Updateable } from 'kysely'; -import { SharedLinks } from 'src/db'; -import { SharedLinkEntity } from 'src/entities/shared-link.entity'; - -export const ISharedLinkRepository = 'ISharedLinkRepository'; - -export type SharedLinkSearchOptions = { - userId: string; - albumId?: string; -}; - -export interface ISharedLinkRepository { - getAll(options: SharedLinkSearchOptions): Promise; - get(userId: string, id: string): Promise; - getByKey(key: Buffer): Promise; - create(entity: Insertable & { assetIds?: string[] }): Promise; - update(entity: Updateable & { id: string; assetIds?: string[] }): Promise; - remove(entity: SharedLinkEntity): Promise; -} diff --git a/server/src/interfaces/stack.interface.ts b/server/src/interfaces/stack.interface.ts deleted file mode 100644 index a9fb8cec76..0000000000 --- a/server/src/interfaces/stack.interface.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Updateable } from 'kysely'; -import { StackEntity } from 'src/entities/stack.entity'; - -export const IStackRepository = 'IStackRepository'; - -export interface StackSearch { - ownerId: string; - primaryAssetId?: string; -} - -export interface IStackRepository { - search(query: StackSearch): Promise; - create(stack: { ownerId: string; assetIds: string[] }): Promise; - update(id: string, entity: Updateable): Promise; - delete(id: string): Promise; - deleteAll(ids: string[]): Promise; - getById(id: string): Promise; -} diff --git a/server/src/interfaces/storage.interface.ts b/server/src/interfaces/storage.interface.ts deleted file mode 100644 index b304d94fef..0000000000 --- a/server/src/interfaces/storage.interface.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { WatchOptions } from 'chokidar'; -import { Stats } from 'node:fs'; -import { FileReadOptions } from 'node:fs/promises'; -import { Readable, Writable } from 'node:stream'; -import { CrawlOptionsDto, WalkOptionsDto } from 'src/dtos/library.dto'; - -export interface ImmichReadStream { - stream: Readable; - type?: string; - length?: number; -} - -export interface ImmichZipStream extends ImmichReadStream { - addFile: (inputPath: string, filename: string) => void; - finalize: () => Promise; -} - -export interface DiskUsage { - available: number; - free: number; - total: number; -} - -export const IStorageRepository = 'IStorageRepository'; - -export interface WatchEvents { - onReady(): void; - onAdd(path: string): void; - onChange(path: string): void; - onUnlink(path: string): void; - onError(error: Error): void; -} - -export interface IStorageRepository { - createZipStream(): ImmichZipStream; - createReadStream(filepath: string, mimeType?: string | null): Promise; - readFile(filepath: string, options?: FileReadOptions): Promise; - createFile(filepath: string, buffer: Buffer): Promise; - createWriteStream(filepath: string): Writable; - createOrOverwriteFile(filepath: string, buffer: Buffer): Promise; - overwriteFile(filepath: string, buffer: Buffer): Promise; - realpath(filepath: string): Promise; - unlink(filepath: string): Promise; - unlinkDir(folder: string, options?: { recursive?: boolean; force?: boolean }): Promise; - removeEmptyDirs(folder: string, self?: boolean): Promise; - checkFileExists(filepath: string, mode?: number): Promise; - mkdirSync(filepath: string): void; - checkDiskUsage(folder: string): Promise; - readdir(folder: string): Promise; - stat(filepath: string): Promise; - crawl(options: CrawlOptionsDto): Promise; - walk(options: WalkOptionsDto): AsyncGenerator; - copyFile(source: string, target: string): Promise; - rename(source: string, target: string): Promise; - watch(paths: string[], options: WatchOptions, events: Partial): () => Promise; - utimes(filepath: string, atime: Date, mtime: Date): Promise; -} diff --git a/server/src/interfaces/tag.interface.ts b/server/src/interfaces/tag.interface.ts deleted file mode 100644 index 16a34d6ac4..0000000000 --- a/server/src/interfaces/tag.interface.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { TagEntity } from 'src/entities/tag.entity'; -import { IBulkAsset } from 'src/utils/asset.util'; - -export const ITagRepository = 'ITagRepository'; - -export type AssetTagItem = { assetId: string; tagId: string }; - -export interface ITagRepository extends IBulkAsset { - getAll(userId: string): Promise; - getByValue(userId: string, value: string): Promise; - upsertValue(request: { userId: string; value: string; parent?: TagEntity }): Promise; - - create(tag: Partial): Promise; - get(id: string): Promise; - update(tag: { id: string } & Partial): Promise; - delete(id: string): Promise; - - upsertAssetTags({ assetId, tagIds }: { assetId: string; tagIds: string[] }): Promise; - upsertAssetIds(items: AssetTagItem[]): Promise; - deleteEmptyTags(): Promise; -} diff --git a/server/src/interfaces/user.interface.ts b/server/src/interfaces/user.interface.ts deleted file mode 100644 index f126b6eb34..0000000000 --- a/server/src/interfaces/user.interface.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Insertable, Updateable } from 'kysely'; -import { Users } from 'src/db'; -import { UserMetadata } from 'src/entities/user-metadata.entity'; -import { UserEntity } from 'src/entities/user.entity'; - -export interface UserListFilter { - withDeleted?: boolean; -} - -export interface UserStatsQueryResponse { - userId: string; - userName: string; - photos: number; - videos: number; - usage: number; - usagePhotos: number; - usageVideos: number; - quotaSizeInBytes: number | null; -} - -export interface UserFindOptions { - withDeleted?: boolean; -} - -export const IUserRepository = 'IUserRepository'; - -export interface IUserRepository { - get(id: string, options: UserFindOptions): Promise; - getAdmin(): Promise; - hasAdmin(): Promise; - getByEmail(email: string, withPassword?: boolean): Promise; - getByStorageLabel(storageLabel: string): Promise; - getByOAuthId(oauthId: string): Promise; - getDeletedUsers(): Promise; - getList(filter?: UserListFilter): Promise; - getUserStats(): Promise; - create(user: Insertable): Promise; - update(id: string, user: Updateable): Promise; - restore(id: string): Promise; - upsertMetadata(id: string, item: { key: T; value: UserMetadata[T] }): Promise; - deleteMetadata(id: string, key: T): Promise; - delete(user: UserEntity, hard?: boolean): Promise; - updateUsage(id: string, delta: number): Promise; - syncUsage(id?: string): Promise; -} diff --git a/server/src/migrations/1700713994428-AddCLIPEmbeddingIndex.ts b/server/src/migrations/1700713994428-AddCLIPEmbeddingIndex.ts index f9ea5a0dc3..993e12f822 100644 --- a/server/src/migrations/1700713994428-AddCLIPEmbeddingIndex.ts +++ b/server/src/migrations/1700713994428-AddCLIPEmbeddingIndex.ts @@ -1,4 +1,4 @@ -import { DatabaseExtension } from 'src/interfaces/database.interface'; +import { DatabaseExtension } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { MigrationInterface, QueryRunner } from 'typeorm'; diff --git a/server/src/migrations/1700714033632-AddFaceEmbeddingIndex.ts b/server/src/migrations/1700714033632-AddFaceEmbeddingIndex.ts index d11e7b921e..182aae4e42 100644 --- a/server/src/migrations/1700714033632-AddFaceEmbeddingIndex.ts +++ b/server/src/migrations/1700714033632-AddFaceEmbeddingIndex.ts @@ -1,4 +1,4 @@ -import { DatabaseExtension } from 'src/interfaces/database.interface'; +import { DatabaseExtension } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { MigrationInterface, QueryRunner } from 'typeorm'; diff --git a/server/src/migrations/1718486162779-AddFaceSearchRelation.ts b/server/src/migrations/1718486162779-AddFaceSearchRelation.ts index ae6d752c65..e08bcb8e25 100644 --- a/server/src/migrations/1718486162779-AddFaceSearchRelation.ts +++ b/server/src/migrations/1718486162779-AddFaceSearchRelation.ts @@ -1,4 +1,4 @@ -import { DatabaseExtension } from 'src/interfaces/database.interface'; +import { DatabaseExtension } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { MigrationInterface, QueryRunner } from 'typeorm'; diff --git a/server/src/repositories/album.repository.ts b/server/src/repositories/album.repository.ts index c09a13750e..1fe2938fcc 100644 --- a/server/src/repositories/album.repository.ts +++ b/server/src/repositories/album.repository.ts @@ -6,7 +6,17 @@ import { Albums, DB } from 'src/db'; import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; import { AlbumUserCreateDto } from 'src/dtos/album.dto'; import { AlbumEntity } from 'src/entities/album.entity'; -import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface'; + +export interface AlbumAssetCount { + albumId: string; + assetCount: number; + startDate: Date | null; + endDate: Date | null; +} + +export interface AlbumInfoOptions { + withAssets: boolean; +} const userColumns = [ 'id', @@ -71,7 +81,7 @@ const withAssets = (eb: ExpressionBuilder) => { }; @Injectable() -export class AlbumRepository implements IAlbumRepository { +export class AlbumRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, { withAssets: true }] }) diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index 2ac81bbf97..bd82cc0724 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -21,34 +21,138 @@ import { withTagId, withTags, } from 'src/entities/asset.entity'; -import { AssetFileType, AssetStatus, AssetType } from 'src/enum'; -import { - AssetDeltaSyncOptions, - AssetExploreFieldOptions, - AssetFullSyncOptions, - AssetGetByChecksumOptions, - AssetStats, - AssetStatsOptions, - AssetUpdateDuplicateOptions, - DayOfYearAssets, - DuplicateGroup, - GetByIdsRelations, - IAssetRepository, - LivePhotoSearchOptions, - MonthDay, - TimeBucketItem, - TimeBucketOptions, - TimeBucketSize, - WithProperty, - WithoutProperty, -} from 'src/interfaces/asset.interface'; -import { AssetSearchOptions, SearchExploreItem, SearchExploreItemSet } from 'src/interfaces/search.interface'; +import { AssetFileType, AssetOrder, AssetStatus, AssetType } from 'src/enum'; import { MapMarker, MapMarkerSearchOptions } from 'src/repositories/map.repository'; +import { AssetSearchOptions, SearchExploreItem, SearchExploreItemSet } from 'src/repositories/search.repository'; import { anyUuid, asUuid, mapUpsertColumns } from 'src/utils/database'; import { Paginated, PaginationOptions, paginationHelper } from 'src/utils/pagination'; +export type AssetStats = Record; + +export interface AssetStatsOptions { + isFavorite?: boolean; + isArchived?: boolean; + isTrashed?: boolean; +} + +export interface LivePhotoSearchOptions { + ownerId: string; + libraryId?: string | null; + livePhotoCID: string; + otherAssetId: string; + type: AssetType; +} + +export enum WithoutProperty { + THUMBNAIL = 'thumbnail', + ENCODED_VIDEO = 'encoded-video', + EXIF = 'exif', + SMART_SEARCH = 'smart-search', + DUPLICATE = 'duplicate', + FACES = 'faces', + SIDECAR = 'sidecar', +} + +export enum WithProperty { + SIDECAR = 'sidecar', +} + +export enum TimeBucketSize { + DAY = 'DAY', + MONTH = 'MONTH', +} + +export interface AssetBuilderOptions { + isArchived?: boolean; + isFavorite?: boolean; + isTrashed?: boolean; + isDuplicate?: boolean; + albumId?: string; + tagId?: string; + personId?: string; + userIds?: string[]; + withStacked?: boolean; + exifInfo?: boolean; + status?: AssetStatus; + assetType?: AssetType; +} + +export interface TimeBucketOptions extends AssetBuilderOptions { + size: TimeBucketSize; + order?: AssetOrder; +} + +export interface TimeBucketItem { + timeBucket: string; + count: number; +} + +export interface MonthDay { + day: number; + month: number; +} + +export interface AssetExploreFieldOptions { + maxFields: number; + minAssetsPerField: number; +} + +export interface AssetFullSyncOptions { + ownerId: string; + lastId?: string; + updatedUntil: Date; + limit: number; +} + +export interface AssetDeltaSyncOptions { + userIds: string[]; + updatedAfter: Date; + limit: number; +} + +export interface AssetUpdateDuplicateOptions { + targetDuplicateId: string | null; + assetIds: string[]; + duplicateIds: string[]; +} + +export interface UpsertFileOptions { + assetId: string; + type: AssetFileType; + path: string; +} + +export interface AssetGetByChecksumOptions { + ownerId: string; + checksum: Buffer; + libraryId?: string; +} + +export type AssetPathEntity = Pick; + +export interface GetByIdsRelations { + exifInfo?: boolean; + faces?: { person?: boolean }; + files?: boolean; + library?: boolean; + owner?: boolean; + smartSearch?: boolean; + stack?: { assets?: boolean }; + tags?: boolean; +} + +export interface DuplicateGroup { + duplicateId: string; + assets: AssetEntity[]; +} + +export interface DayOfYearAssets { + yearsAgo: number; + assets: AssetEntity[]; +} + @Injectable() -export class AssetRepository implements IAssetRepository { +export class AssetRepository { constructor(@InjectKysely() private db: Kysely) {} async upsertExif(exif: Insertable): Promise { diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index 5b04914dac..27c10bcdcc 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -13,9 +13,9 @@ import { Notice } from 'postgres'; import { citiesFile, excludePaths, IWorker } from 'src/constants'; import { Telemetry } from 'src/decorators'; import { EnvDto } from 'src/dtos/env.dto'; -import { ImmichEnvironment, ImmichHeader, ImmichTelemetry, ImmichWorker, LogLevel } from 'src/enum'; -import { DatabaseConnectionParams, DatabaseExtension, VectorExtension } from 'src/interfaces/database.interface'; +import { DatabaseExtension, ImmichEnvironment, ImmichHeader, ImmichTelemetry, ImmichWorker, LogLevel } from 'src/enum'; import { QueueName } from 'src/interfaces/job.interface'; +import { DatabaseConnectionParams, VectorExtension } from 'src/repositories/database.repository'; import { setDifference } from 'src/utils/set'; import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js'; diff --git a/server/src/repositories/crypto.repository.ts b/server/src/repositories/crypto.repository.ts index ee25609fec..e471ccb031 100644 --- a/server/src/repositories/crypto.repository.ts +++ b/server/src/repositories/crypto.repository.ts @@ -2,11 +2,10 @@ import { Injectable } from '@nestjs/common'; import { compareSync, hash } from 'bcrypt'; import { createHash, createPublicKey, createVerify, randomBytes, randomUUID } from 'node:crypto'; import { createReadStream } from 'node:fs'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; @Injectable() -export class CryptoRepository implements ICryptoRepository { - randomUUID() { +export class CryptoRepository { + randomUUID(): string { return randomUUID(); } diff --git a/server/src/repositories/database.repository.ts b/server/src/repositories/database.repository.ts index f7d52efd7a..6edcedd13c 100644 --- a/server/src/repositories/database.repository.ts +++ b/server/src/repositories/database.repository.ts @@ -6,24 +6,66 @@ import { InjectKysely } from 'nestjs-kysely'; import semver from 'semver'; import { POSTGRES_VERSION_RANGE, VECTOR_VERSION_RANGE, VECTORS_VERSION_RANGE } from 'src/constants'; import { DB } from 'src/db'; -import { - DatabaseExtension, - DatabaseLock, - EXTENSION_NAMES, - ExtensionVersion, - IDatabaseRepository, - VectorExtension, - VectorIndex, - VectorUpdateResult, -} from 'src/interfaces/database.interface'; +import { DatabaseExtension } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { UPSERT_COLUMNS } from 'src/utils/database'; import { isValidInteger } from 'src/validation'; import { DataSource, EntityManager, EntityMetadata, QueryRunner } from 'typeorm'; +export type VectorExtension = DatabaseExtension.VECTOR | DatabaseExtension.VECTORS; + +export type DatabaseConnectionURL = { + connectionType: 'url'; + url: string; +}; + +export type DatabaseConnectionParts = { + connectionType: 'parts'; + host: string; + port: number; + username: string; + password: string; + database: string; +}; + +export type DatabaseConnectionParams = DatabaseConnectionURL | DatabaseConnectionParts; + +export enum VectorIndex { + CLIP = 'clip_index', + FACE = 'face_index', +} + +export enum DatabaseLock { + GeodataImport = 100, + Migrations = 200, + SystemFileMounts = 300, + StorageTemplateMigration = 420, + VersionHistory = 500, + CLIPDimSize = 512, + Library = 1337, + GetSystemConfig = 69, + BackupDatabase = 42, +} + +export const EXTENSION_NAMES: Record = { + cube: 'cube', + earthdistance: 'earthdistance', + vector: 'pgvector', + vectors: 'pgvecto.rs', +} as const; + +export interface ExtensionVersion { + availableVersion: string | null; + installedVersion: string | null; +} + +export interface VectorUpdateResult { + restartRequired: boolean; +} + @Injectable() -export class DatabaseRepository implements IDatabaseRepository { +export class DatabaseRepository { private vectorExtension: VectorExtension; private readonly asyncLock = new AsyncLock(); diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index cb870bd339..d9ef84fc06 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -1,20 +1,6 @@ -import { IAlbumRepository } from 'src/interfaces/album.interface'; -import { IAssetRepository } from 'src/interfaces/asset.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 { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; -import { IMoveRepository } from 'src/interfaces/move.interface'; -import { IPartnerRepository } from 'src/interfaces/partner.interface'; -import { IPersonRepository } from 'src/interfaces/person.interface'; -import { ISearchRepository } from 'src/interfaces/search.interface'; -import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; -import { IStackRepository } from 'src/interfaces/stack.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { ITagRepository } from 'src/interfaces/tag.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; import { AccessRepository } from 'src/repositories/access.repository'; import { ActivityRepository } from 'src/repositories/activity.repository'; import { AlbumUserRepository } from 'src/repositories/album-user.repository'; @@ -58,44 +44,44 @@ import { ViewRepository } from 'src/repositories/view-repository'; export const repositories = [ AccessRepository, ActivityRepository, + AlbumRepository, AlbumUserRepository, AuditRepository, ApiKeyRepository, + AssetRepository, ConfigRepository, CronRepository, + CryptoRepository, + DatabaseRepository, + LibraryRepository, LoggingRepository, MapRepository, MediaRepository, MemoryRepository, MetadataRepository, + MoveRepository, NotificationRepository, OAuthRepository, + PartnerRepository, + PersonRepository, ProcessRepository, + SearchRepository, SessionRepository, ServerInfoRepository, + SharedLinkRepository, + StackRepository, + StorageRepository, SystemMetadataRepository, + TagRepository, TelemetryRepository, TrashRepository, + UserRepository, ViewRepository, VersionHistoryRepository, ]; export const providers = [ - { provide: IAlbumRepository, useClass: AlbumRepository }, - { provide: IAssetRepository, useClass: AssetRepository }, - { provide: ICryptoRepository, useClass: CryptoRepository }, - { provide: IDatabaseRepository, useClass: DatabaseRepository }, { provide: IEventRepository, useClass: EventRepository }, { provide: IJobRepository, useClass: JobRepository }, - { provide: ILibraryRepository, useClass: LibraryRepository }, { provide: IMachineLearningRepository, useClass: MachineLearningRepository }, - { provide: IMoveRepository, useClass: MoveRepository }, - { provide: IPartnerRepository, useClass: PartnerRepository }, - { provide: IPersonRepository, useClass: PersonRepository }, - { provide: ISearchRepository, useClass: SearchRepository }, - { provide: ISharedLinkRepository, useClass: SharedLinkRepository }, - { provide: IStackRepository, useClass: StackRepository }, - { provide: IStorageRepository, useClass: StorageRepository }, - { provide: ITagRepository, useClass: TagRepository }, - { provide: IUserRepository, useClass: UserRepository }, ]; diff --git a/server/src/repositories/library.repository.ts b/server/src/repositories/library.repository.ts index 0e1ec94c32..f748600fbb 100644 --- a/server/src/repositories/library.repository.ts +++ b/server/src/repositories/library.repository.ts @@ -7,7 +7,6 @@ import { DummyValue, GenerateSql } from 'src/decorators'; import { LibraryStatsResponseDto } from 'src/dtos/library.dto'; import { LibraryEntity } from 'src/entities/library.entity'; import { AssetType } from 'src/enum'; -import { ILibraryRepository } from 'src/interfaces/library.interface'; const userColumns = [ 'users.id', @@ -34,7 +33,7 @@ const withOwner = (eb: ExpressionBuilder) => { }; @Injectable() -export class LibraryRepository implements ILibraryRepository { +export class LibraryRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID] }) diff --git a/server/src/repositories/move.repository.ts b/server/src/repositories/move.repository.ts index c0177f3f30..c46259fa9b 100644 --- a/server/src/repositories/move.repository.ts +++ b/server/src/repositories/move.repository.ts @@ -5,10 +5,11 @@ import { DB, MoveHistory } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { MoveEntity } from 'src/entities/move.entity'; import { PathType } from 'src/enum'; -import { IMoveRepository } from 'src/interfaces/move.interface'; + +export type MoveCreate = Pick & Partial; @Injectable() -export class MoveRepository implements IMoveRepository { +export class MoveRepository { constructor(@InjectKysely() private db: Kysely) {} create(entity: Insertable): Promise { diff --git a/server/src/repositories/partner.repository.ts b/server/src/repositories/partner.repository.ts index 929c06a1f5..f799ff56f2 100644 --- a/server/src/repositories/partner.repository.ts +++ b/server/src/repositories/partner.repository.ts @@ -5,7 +5,16 @@ import { InjectKysely } from 'nestjs-kysely'; import { DB, Partners, Users } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { PartnerEntity } from 'src/entities/partner.entity'; -import { IPartnerRepository, PartnerIds } from 'src/interfaces/partner.interface'; + +export interface PartnerIds { + sharedById: string; + sharedWithId: string; +} + +export enum PartnerDirection { + SharedBy = 'shared-by', + SharedWith = 'shared-with', +} const columns = ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'] as const; @@ -28,7 +37,7 @@ const withSharedWith = (eb: ExpressionBuilder) => { }; @Injectable() -export class PartnerRepository implements IPartnerRepository { +export class PartnerRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID] }) diff --git a/server/src/repositories/person.repository.ts b/server/src/repositories/person.repository.ts index 73fb8313d2..b862d66f8a 100644 --- a/server/src/repositories/person.repository.ts +++ b/server/src/repositories/person.repository.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { ExpressionBuilder, Insertable, Kysely, sql } from 'kysely'; +import { ExpressionBuilder, Insertable, Kysely, Selectable, sql } from 'kysely'; import { jsonObjectFrom } from 'kysely/helpers/postgres'; import { InjectKysely } from 'nestjs-kysely'; import { AssetFaces, DB, FaceSearch, Person } from 'src/db'; @@ -7,23 +7,53 @@ import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { SourceType } from 'src/enum'; -import { - AssetFaceId, - DeleteFacesOptions, - IPersonRepository, - PeopleStatistics, - PersonNameResponse, - PersonNameSearchOptions, - PersonSearchOptions, - PersonStatistics, - SelectFaceOptions, - UnassignFacesOptions, - UpdateFacesData, -} from 'src/interfaces/person.interface'; import { mapUpsertColumns } from 'src/utils/database'; import { Paginated, PaginationOptions } from 'src/utils/pagination'; import { FindOptionsRelations } from 'typeorm'; +export interface PersonSearchOptions { + minimumFaceCount: number; + withHidden: boolean; + closestFaceAssetId?: string; +} + +export interface PersonNameSearchOptions { + withHidden?: boolean; +} + +export interface PersonNameResponse { + id: string; + name: string; +} + +export interface AssetFaceId { + assetId: string; + personId: string; +} + +export interface UpdateFacesData { + oldPersonId?: string; + faceIds?: string[]; + newPersonId: string; +} + +export interface PersonStatistics { + assets: number; +} + +export interface PeopleStatistics { + total: number; + hidden: number; +} + +export interface DeleteFacesOptions { + sourceType: SourceType; +} + +export type UnassignFacesOptions = DeleteFacesOptions; + +export type SelectFaceOptions = (keyof Selectable)[]; + const withPerson = (eb: ExpressionBuilder) => { return jsonObjectFrom( eb.selectFrom('person').selectAll('person').whereRef('person.id', '=', 'asset_faces.personId'), @@ -43,7 +73,7 @@ const withFaceSearch = (eb: ExpressionBuilder) => { }; @Injectable() -export class PersonRepository implements IPersonRepository { +export class PersonRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [{ oldPersonId: DummyValue.UUID, newPersonId: DummyValue.UUID }] }) diff --git a/server/src/repositories/search.repository.ts b/server/src/repositories/search.repository.ts index 76b6653e3d..a6eb5c7a85 100644 --- a/server/src/repositories/search.repository.ts +++ b/server/src/repositories/search.repository.ts @@ -6,26 +6,202 @@ import { DB } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { AssetEntity, searchAssetBuilder } from 'src/entities/asset.entity'; import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity'; -import { AssetType } from 'src/enum'; -import { - AssetDuplicateSearch, - AssetSearchOptions, - FaceEmbeddingSearch, - GetCameraMakesOptions, - GetCameraModelsOptions, - GetCitiesOptions, - GetStatesOptions, - ISearchRepository, - SearchPaginationOptions, - SmartSearchOptions, -} from 'src/interfaces/search.interface'; +import { AssetStatus, AssetType } from 'src/enum'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { anyUuid, asUuid } from 'src/utils/database'; import { Paginated } from 'src/utils/pagination'; import { isValidInteger } from 'src/validation'; +export interface SearchResult { + /** total matches */ + total: number; + /** collection size */ + count: number; + /** current page */ + page: number; + /** items for page */ + items: T[]; + /** score */ + distances: number[]; + facets: SearchFacet[]; +} + +export interface SearchFacet { + fieldName: string; + counts: Array<{ + count: number; + value: string; + }>; +} + +export type SearchExploreItemSet = Array<{ + value: string; + data: T; +}>; + +export interface SearchExploreItem { + fieldName: string; + items: SearchExploreItemSet; +} + +export interface SearchAssetIDOptions { + checksum?: Buffer; + deviceAssetId?: string; + id?: string; +} + +export interface SearchUserIdOptions { + deviceId?: string; + libraryId?: string | null; + userIds?: string[]; +} + +export type SearchIdOptions = SearchAssetIDOptions & SearchUserIdOptions; + +export interface SearchStatusOptions { + isArchived?: boolean; + isEncoded?: boolean; + isFavorite?: boolean; + isMotion?: boolean; + isOffline?: boolean; + isVisible?: boolean; + isNotInAlbum?: boolean; + type?: AssetType; + status?: AssetStatus; + withArchived?: boolean; + withDeleted?: boolean; +} + +export interface SearchOneToOneRelationOptions { + withExif?: boolean; + withStacked?: boolean; +} + +export interface SearchRelationOptions extends SearchOneToOneRelationOptions { + withFaces?: boolean; + withPeople?: boolean; +} + +export interface SearchDateOptions { + createdBefore?: Date; + createdAfter?: Date; + takenBefore?: Date; + takenAfter?: Date; + trashedBefore?: Date; + trashedAfter?: Date; + updatedBefore?: Date; + updatedAfter?: Date; +} + +export interface SearchPathOptions { + encodedVideoPath?: string; + originalFileName?: string; + originalPath?: string; + previewPath?: string; + thumbnailPath?: string; +} + +export interface SearchExifOptions { + city?: string | null; + country?: string | null; + lensModel?: string | null; + make?: string | null; + model?: string | null; + state?: string | null; + description?: string | null; +} + +export interface SearchEmbeddingOptions { + embedding: string; + userIds: string[]; +} + +export interface SearchPeopleOptions { + personIds?: string[]; +} + +export interface SearchTagOptions { + tagIds?: string[]; +} + +export interface SearchOrderOptions { + orderDirection?: 'asc' | 'desc'; +} + +export interface SearchPaginationOptions { + page: number; + size: number; +} + +type BaseAssetSearchOptions = SearchDateOptions & + SearchIdOptions & + SearchExifOptions & + SearchOrderOptions & + SearchPathOptions & + SearchStatusOptions & + SearchUserIdOptions & + SearchPeopleOptions & + SearchTagOptions; + +export type AssetSearchOptions = BaseAssetSearchOptions & SearchRelationOptions; + +export type AssetSearchOneToOneRelationOptions = BaseAssetSearchOptions & SearchOneToOneRelationOptions; + +export type AssetSearchBuilderOptions = Omit; + +export type SmartSearchOptions = SearchDateOptions & + SearchEmbeddingOptions & + SearchExifOptions & + SearchOneToOneRelationOptions & + SearchStatusOptions & + SearchUserIdOptions & + SearchPeopleOptions & + SearchTagOptions; + +export interface FaceEmbeddingSearch extends SearchEmbeddingOptions { + hasPerson?: boolean; + numResults: number; + maxDistance: number; +} + +export interface AssetDuplicateSearch { + assetId: string; + embedding: string; + maxDistance: number; + type: AssetType; + userIds: string[]; +} + +export interface FaceSearchResult { + distance: number; + id: string; + personId: string | null; +} + +export interface AssetDuplicateResult { + assetId: string; + duplicateId: string | null; + distance: number; +} + +export interface GetStatesOptions { + country?: string; +} + +export interface GetCitiesOptions extends GetStatesOptions { + state?: string; +} + +export interface GetCameraModelsOptions { + make?: string; +} + +export interface GetCameraMakesOptions { + model?: string; +} + @Injectable() -export class SearchRepository implements ISearchRepository { +export class SearchRepository { constructor( private logger: LoggingRepository, @InjectKysely() private db: Kysely, diff --git a/server/src/repositories/shared-link.repository.ts b/server/src/repositories/shared-link.repository.ts index 8e2e6976a5..16dc48836a 100644 --- a/server/src/repositories/shared-link.repository.ts +++ b/server/src/repositories/shared-link.repository.ts @@ -7,10 +7,14 @@ import { DB, SharedLinks } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { SharedLinkType } from 'src/enum'; -import { ISharedLinkRepository, SharedLinkSearchOptions } from 'src/interfaces/shared-link.interface'; + +export type SharedLinkSearchOptions = { + userId: string; + albumId?: string; +}; @Injectable() -export class SharedLinkRepository implements ISharedLinkRepository { +export class SharedLinkRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) diff --git a/server/src/repositories/stack.repository.ts b/server/src/repositories/stack.repository.ts index 018d7e77a4..ae96005350 100644 --- a/server/src/repositories/stack.repository.ts +++ b/server/src/repositories/stack.repository.ts @@ -5,9 +5,13 @@ import { InjectKysely } from 'nestjs-kysely'; import { DB } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { StackEntity } from 'src/entities/stack.entity'; -import { IStackRepository, StackSearch } from 'src/interfaces/stack.interface'; import { asUuid } from 'src/utils/database'; +export interface StackSearch { + ownerId: string; + primaryAssetId?: string; +} + const withAssets = (eb: ExpressionBuilder, withTags = false) => { return jsonArrayFrom( eb @@ -35,7 +39,7 @@ const withAssets = (eb: ExpressionBuilder, withTags = false) }; @Injectable() -export class StackRepository implements IStackRepository { +export class StackRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [{ ownerId: DummyValue.UUID }] }) diff --git a/server/src/repositories/storage.repository.ts b/server/src/repositories/storage.repository.ts index 6766f442b8..15b81e7106 100644 --- a/server/src/repositories/storage.repository.ts +++ b/server/src/repositories/storage.repository.ts @@ -5,20 +5,38 @@ import { escapePath, glob, globStream } from 'fast-glob'; import { constants, createReadStream, createWriteStream, existsSync, mkdirSync } from 'node:fs'; import fs from 'node:fs/promises'; import path from 'node:path'; -import { Writable } from 'node:stream'; +import { Readable, Writable } from 'node:stream'; import { CrawlOptionsDto, WalkOptionsDto } from 'src/dtos/library.dto'; -import { - DiskUsage, - IStorageRepository, - ImmichReadStream, - ImmichZipStream, - WatchEvents, -} from 'src/interfaces/storage.interface'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { mimeTypes } from 'src/utils/mime-types'; +export interface WatchEvents { + onReady(): void; + onAdd(path: string): void; + onChange(path: string): void; + onUnlink(path: string): void; + onError(error: Error): void; +} + +export interface ImmichReadStream { + stream: Readable; + type?: string; + length?: number; +} + +export interface ImmichZipStream extends ImmichReadStream { + addFile: (inputPath: string, filename: string) => void; + finalize: () => Promise; +} + +export interface DiskUsage { + available: number; + free: number; + total: number; +} + @Injectable() -export class StorageRepository implements IStorageRepository { +export class StorageRepository { constructor(private logger: LoggingRepository) { this.logger.setContext(StorageRepository.name); } diff --git a/server/src/repositories/tag.repository.ts b/server/src/repositories/tag.repository.ts index 3489f43640..c5156e1837 100644 --- a/server/src/repositories/tag.repository.ts +++ b/server/src/repositories/tag.repository.ts @@ -2,12 +2,13 @@ import { Injectable } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; import { TagEntity } from 'src/entities/tag.entity'; -import { AssetTagItem, ITagRepository } from 'src/interfaces/tag.interface'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { DataSource, In, Repository } from 'typeorm'; +export type AssetTagItem = { assetId: string; tagId: string }; + @Injectable() -export class TagRepository implements ITagRepository { +export class TagRepository { constructor( @InjectDataSource() private dataSource: DataSource, @InjectRepository(TagEntity) private repository: Repository, diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index 417ee141f4..22a9ecad5c 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -6,12 +6,6 @@ import { DummyValue, GenerateSql } from 'src/decorators'; import { UserMetadata } from 'src/entities/user-metadata.entity'; import { UserEntity, withMetadata } from 'src/entities/user.entity'; import { UserStatus } from 'src/enum'; -import { - IUserRepository, - UserFindOptions, - UserListFilter, - UserStatsQueryResponse, -} from 'src/interfaces/user.interface'; import { asUuid } from 'src/utils/database'; const columns = [ @@ -34,8 +28,27 @@ const columns = [ type Upsert = Insertable; +export interface UserListFilter { + withDeleted?: boolean; +} + +export interface UserStatsQueryResponse { + userId: string; + userName: string; + photos: number; + videos: number; + usage: number; + usagePhotos: number; + usageVideos: number; + quotaSizeInBytes: number | null; +} + +export interface UserFindOptions { + withDeleted?: boolean; +} + @Injectable() -export class UserRepository implements IUserRepository { +export class UserRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.BOOLEAN] }) diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index 0286b387c3..722745ebd2 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -16,7 +16,7 @@ import { AuthDto } from 'src/dtos/auth.dto'; import { AlbumUserEntity } from 'src/entities/album-user.entity'; import { AlbumEntity } from 'src/entities/album.entity'; import { Permission } from 'src/enum'; -import { AlbumAssetCount, AlbumInfoOptions } from 'src/interfaces/album.interface'; +import { AlbumAssetCount, AlbumInfoOptions } from 'src/repositories/album.repository'; import { BaseService } from 'src/services/base.service'; import { addAssets, removeAssets } from 'src/utils/asset.util'; diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index 9d64aacf10..3c9cf3739f 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -4,8 +4,8 @@ import { mapAsset } from 'src/dtos/asset-response.dto'; import { AssetJobName, AssetStatsResponseDto } from 'src/dtos/asset.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { AssetStatus, AssetType } from 'src/enum'; -import { AssetStats } from 'src/interfaces/asset.interface'; import { JobName, JobStatus } from 'src/interfaces/job.interface'; +import { AssetStats } from 'src/repositories/asset.repository'; import { AssetService } from 'src/services/asset.service'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; diff --git a/server/src/services/backup.service.ts b/server/src/services/backup.service.ts index 324bc4cc13..d0a8ce69b6 100644 --- a/server/src/services/backup.service.ts +++ b/server/src/services/backup.service.ts @@ -4,9 +4,9 @@ import semver from 'semver'; import { StorageCore } from 'src/cores/storage.core'; import { OnEvent, OnJob } from 'src/decorators'; import { ImmichWorker, StorageFolder } from 'src/enum'; -import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; import { JobName, JobStatus, QueueName } from 'src/interfaces/job.interface'; +import { DatabaseLock } from 'src/repositories/database.repository'; import { BaseService } from 'src/services/base.service'; import { handlePromiseError } from 'src/utils/misc'; diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index 9285c69ced..d0f00bd2ab 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -6,44 +6,43 @@ import { SALT_ROUNDS } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; import { Users } from 'src/db'; import { UserEntity } from 'src/entities/user.entity'; -import { IAlbumRepository } from 'src/interfaces/album.interface'; -import { IAssetRepository } from 'src/interfaces/asset.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 { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; -import { IMoveRepository } from 'src/interfaces/move.interface'; -import { IPartnerRepository } from 'src/interfaces/partner.interface'; -import { IPersonRepository } from 'src/interfaces/person.interface'; -import { ISearchRepository } from 'src/interfaces/search.interface'; -import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; -import { IStackRepository } from 'src/interfaces/stack.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { ITagRepository } from 'src/interfaces/tag.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; import { AccessRepository } from 'src/repositories/access.repository'; import { ActivityRepository } from 'src/repositories/activity.repository'; import { AlbumUserRepository } from 'src/repositories/album-user.repository'; +import { AlbumRepository } from 'src/repositories/album.repository'; import { ApiKeyRepository } from 'src/repositories/api-key.repository'; +import { AssetRepository } from 'src/repositories/asset.repository'; import { AuditRepository } from 'src/repositories/audit.repository'; import { ConfigRepository } from 'src/repositories/config.repository'; import { CronRepository } from 'src/repositories/cron.repository'; import { CryptoRepository } from 'src/repositories/crypto.repository'; +import { DatabaseRepository } from 'src/repositories/database.repository'; +import { LibraryRepository } from 'src/repositories/library.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { MapRepository } from 'src/repositories/map.repository'; import { MediaRepository } from 'src/repositories/media.repository'; import { MemoryRepository } from 'src/repositories/memory.repository'; import { MetadataRepository } from 'src/repositories/metadata.repository'; +import { MoveRepository } from 'src/repositories/move.repository'; import { NotificationRepository } from 'src/repositories/notification.repository'; import { OAuthRepository } from 'src/repositories/oauth.repository'; +import { PartnerRepository } from 'src/repositories/partner.repository'; +import { PersonRepository } from 'src/repositories/person.repository'; import { ProcessRepository } from 'src/repositories/process.repository'; +import { SearchRepository } from 'src/repositories/search.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository'; import { SessionRepository } from 'src/repositories/session.repository'; +import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; +import { StackRepository } from 'src/repositories/stack.repository'; +import { StorageRepository } from 'src/repositories/storage.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; +import { TagRepository } from 'src/repositories/tag.repository'; import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TrashRepository } from 'src/repositories/trash.repository'; +import { UserRepository } from 'src/repositories/user.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; import { ViewRepository } from 'src/repositories/view-repository'; import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access'; @@ -57,39 +56,39 @@ export class BaseService { protected accessRepository: AccessRepository, protected activityRepository: ActivityRepository, protected auditRepository: AuditRepository, - @Inject(IAlbumRepository) protected albumRepository: IAlbumRepository, + protected albumRepository: AlbumRepository, protected albumUserRepository: AlbumUserRepository, - @Inject(IAssetRepository) protected assetRepository: IAssetRepository, + protected assetRepository: AssetRepository, protected configRepository: ConfigRepository, protected cronRepository: CronRepository, - @Inject(ICryptoRepository) protected cryptoRepository: CryptoRepository, - @Inject(IDatabaseRepository) protected databaseRepository: IDatabaseRepository, + protected cryptoRepository: CryptoRepository, + protected databaseRepository: DatabaseRepository, @Inject(IEventRepository) protected eventRepository: IEventRepository, @Inject(IJobRepository) protected jobRepository: IJobRepository, protected keyRepository: ApiKeyRepository, - @Inject(ILibraryRepository) protected libraryRepository: ILibraryRepository, + protected libraryRepository: LibraryRepository, @Inject(IMachineLearningRepository) protected machineLearningRepository: IMachineLearningRepository, protected mapRepository: MapRepository, protected mediaRepository: MediaRepository, protected memoryRepository: MemoryRepository, protected metadataRepository: MetadataRepository, - @Inject(IMoveRepository) protected moveRepository: IMoveRepository, + protected moveRepository: MoveRepository, protected notificationRepository: NotificationRepository, protected oauthRepository: OAuthRepository, - @Inject(IPartnerRepository) protected partnerRepository: IPartnerRepository, - @Inject(IPersonRepository) protected personRepository: IPersonRepository, + protected partnerRepository: PartnerRepository, + protected personRepository: PersonRepository, protected processRepository: ProcessRepository, - @Inject(ISearchRepository) protected searchRepository: ISearchRepository, + protected searchRepository: SearchRepository, protected serverInfoRepository: ServerInfoRepository, protected sessionRepository: SessionRepository, - @Inject(ISharedLinkRepository) protected sharedLinkRepository: ISharedLinkRepository, - @Inject(IStackRepository) protected stackRepository: IStackRepository, - @Inject(IStorageRepository) protected storageRepository: IStorageRepository, + protected sharedLinkRepository: SharedLinkRepository, + protected stackRepository: StackRepository, + protected storageRepository: StorageRepository, protected systemMetadataRepository: SystemMetadataRepository, - @Inject(ITagRepository) protected tagRepository: ITagRepository, + protected tagRepository: TagRepository, protected telemetryRepository: TelemetryRepository, protected trashRepository: TrashRepository, - @Inject(IUserRepository) protected userRepository: IUserRepository, + protected userRepository: UserRepository, protected versionRepository: VersionHistoryRepository, protected viewRepository: ViewRepository, ) { diff --git a/server/src/services/database.service.spec.ts b/server/src/services/database.service.spec.ts index 566cd32778..d50b7facf6 100644 --- a/server/src/services/database.service.spec.ts +++ b/server/src/services/database.service.spec.ts @@ -1,4 +1,5 @@ -import { DatabaseExtension, EXTENSION_NAMES, VectorExtension } from 'src/interfaces/database.interface'; +import { DatabaseExtension } from 'src/enum'; +import { EXTENSION_NAMES, VectorExtension } from 'src/repositories/database.repository'; import { DatabaseService } from 'src/services/database.service'; import { mockEnvData } from 'test/repositories/config.repository.mock'; import { newTestService, ServiceMocks } from 'test/utils'; diff --git a/server/src/services/database.service.ts b/server/src/services/database.service.ts index ec0075b119..eabb0b0091 100644 --- a/server/src/services/database.service.ts +++ b/server/src/services/database.service.ts @@ -2,14 +2,9 @@ import { Injectable } from '@nestjs/common'; import { Duration } from 'luxon'; import semver from 'semver'; import { OnEvent } from 'src/decorators'; -import { - DatabaseExtension, - DatabaseLock, - EXTENSION_NAMES, - VectorExtension, - VectorIndex, -} from 'src/interfaces/database.interface'; +import { DatabaseExtension } from 'src/enum'; import { BootstrapEventPriority } from 'src/interfaces/event.interface'; +import { DatabaseLock, EXTENSION_NAMES, VectorExtension, VectorIndex } from 'src/repositories/database.repository'; import { BaseService } from 'src/services/base.service'; type CreateFailedArgs = { name: string; extension: string; otherName: string }; diff --git a/server/src/services/download.service.ts b/server/src/services/download.service.ts index 3d66f009cf..dd2430778a 100644 --- a/server/src/services/download.service.ts +++ b/server/src/services/download.service.ts @@ -6,7 +6,7 @@ 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 { ImmichReadStream } from 'src/interfaces/storage.interface'; +import { ImmichReadStream } from 'src/repositories/storage.repository'; import { BaseService } from 'src/services/base.service'; import { HumanReadableSize } from 'src/utils/bytes'; import { usePagination } from 'src/utils/pagination'; diff --git a/server/src/services/duplicate.service.spec.ts b/server/src/services/duplicate.service.spec.ts index 0451f1f2b3..7a8b9ed27b 100644 --- a/server/src/services/duplicate.service.spec.ts +++ b/server/src/services/duplicate.service.spec.ts @@ -1,5 +1,5 @@ -import { WithoutProperty } from 'src/interfaces/asset.interface'; import { JobName, JobStatus } from 'src/interfaces/job.interface'; +import { WithoutProperty } from 'src/repositories/asset.repository'; import { DuplicateService } from 'src/services/duplicate.service'; import { SearchService } from 'src/services/search.service'; import { assetStub } from 'test/fixtures/asset.stub'; diff --git a/server/src/services/duplicate.service.ts b/server/src/services/duplicate.service.ts index 7e8ea49991..1b0d583cc3 100644 --- a/server/src/services/duplicate.service.ts +++ b/server/src/services/duplicate.service.ts @@ -4,9 +4,9 @@ import { mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { DuplicateResponseDto } from 'src/dtos/duplicate.dto'; import { AssetEntity } from 'src/entities/asset.entity'; -import { WithoutProperty } from 'src/interfaces/asset.interface'; import { JOBS_ASSET_PAGINATION_SIZE, JobName, JobOf, JobStatus, QueueName } from 'src/interfaces/job.interface'; -import { AssetDuplicateResult } from 'src/interfaces/search.interface'; +import { WithoutProperty } from 'src/repositories/asset.repository'; +import { AssetDuplicateResult } from 'src/repositories/search.repository'; import { BaseService } from 'src/services/base.service'; import { getAssetFiles } from 'src/utils/asset.util'; import { isDuplicateDetectionEnabled } from 'src/utils/misc'; diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts index 59ac171ce6..5235b786e9 100644 --- a/server/src/services/library.service.ts +++ b/server/src/services/library.service.ts @@ -17,9 +17,9 @@ import { import { AssetEntity } from 'src/entities/asset.entity'; import { LibraryEntity } from 'src/entities/library.entity'; import { AssetType, ImmichWorker } from 'src/enum'; -import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; import { JobName, JobOf, JOBS_LIBRARY_PAGINATION_SIZE, JobStatus, QueueName } from 'src/interfaces/job.interface'; +import { DatabaseLock } from 'src/repositories/database.repository'; import { BaseService } from 'src/services/base.service'; import { mimeTypes } from 'src/utils/mime-types'; import { handlePromiseError } from 'src/utils/misc'; diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index 3f48d8534a..52ccab1c91 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -13,8 +13,8 @@ import { TranscodePolicy, VideoCodec, } from 'src/enum'; -import { WithoutProperty } from 'src/interfaces/asset.interface'; import { JobCounts, JobName, JobStatus } from 'src/interfaces/job.interface'; +import { WithoutProperty } from 'src/repositories/asset.repository'; import { MediaService } from 'src/services/media.service'; import { RawImageInfo } from 'src/types'; import { assetStub } from 'test/fixtures/asset.stub'; diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts index c22d124b63..58621c1b19 100644 --- a/server/src/services/media.service.ts +++ b/server/src/services/media.service.ts @@ -18,7 +18,6 @@ import { VideoCodec, VideoContainer, } from 'src/enum'; -import { UpsertFileOptions, WithoutProperty } from 'src/interfaces/asset.interface'; import { JOBS_ASSET_PAGINATION_SIZE, JobItem, @@ -27,6 +26,7 @@ import { JobStatus, QueueName, } from 'src/interfaces/job.interface'; +import { UpsertFileOptions, WithoutProperty } from 'src/repositories/asset.repository'; import { BaseService } from 'src/services/base.service'; import { AudioStreamInfo, VideoFormat, VideoInterfaces, VideoStreamInfo } from 'src/types'; import { getAssetFiles } from 'src/utils/asset.util'; diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index 5657dd02b9..c7c675ab3e 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -5,8 +5,8 @@ import { constants } from 'node:fs/promises'; import { AssetEntity } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; import { AssetType, ExifOrientation, ImmichWorker, SourceType } from 'src/enum'; -import { WithoutProperty } from 'src/interfaces/asset.interface'; import { JobName, JobStatus } from 'src/interfaces/job.interface'; +import { WithoutProperty } from 'src/repositories/asset.repository'; import { ImmichTags } from 'src/repositories/metadata.repository'; import { MetadataService } from 'src/services/metadata.service'; import { assetStub } from 'test/fixtures/asset.stub'; diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index db3af9fca0..53110a20e0 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -14,10 +14,10 @@ import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetEntity } from 'src/entities/asset.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { AssetType, ExifOrientation, ImmichWorker, SourceType } from 'src/enum'; -import { WithoutProperty } from 'src/interfaces/asset.interface'; -import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; import { JobName, JobOf, JOBS_ASSET_PAGINATION_SIZE, JobStatus, QueueName } from 'src/interfaces/job.interface'; +import { WithoutProperty } from 'src/repositories/asset.repository'; +import { DatabaseLock } from 'src/repositories/database.repository'; import { ReverseGeocodeResult } from 'src/repositories/map.repository'; import { ImmichTags } from 'src/repositories/metadata.repository'; import { BaseService } from 'src/services/base.service'; diff --git a/server/src/services/partner.service.spec.ts b/server/src/services/partner.service.spec.ts index 02dff32a72..9c29afaeaa 100644 --- a/server/src/services/partner.service.spec.ts +++ b/server/src/services/partner.service.spec.ts @@ -1,5 +1,5 @@ import { BadRequestException } from '@nestjs/common'; -import { PartnerDirection } from 'src/interfaces/partner.interface'; +import { PartnerDirection } from 'src/repositories/partner.repository'; import { PartnerService } from 'src/services/partner.service'; import { authStub } from 'test/fixtures/auth.stub'; import { partnerStub } from 'test/fixtures/partner.stub'; diff --git a/server/src/services/partner.service.ts b/server/src/services/partner.service.ts index f17bab24ba..32b3ae3d3f 100644 --- a/server/src/services/partner.service.ts +++ b/server/src/services/partner.service.ts @@ -4,7 +4,7 @@ import { PartnerResponseDto, PartnerSearchDto, UpdatePartnerDto } from 'src/dtos import { mapUser } from 'src/dtos/user.dto'; import { PartnerEntity } from 'src/entities/partner.entity'; import { Permission } from 'src/enum'; -import { PartnerDirection, PartnerIds } from 'src/interfaces/partner.interface'; +import { PartnerDirection, PartnerIds } from 'src/repositories/partner.repository'; import { BaseService } from 'src/services/base.service'; @Injectable() diff --git a/server/src/services/person.service.spec.ts b/server/src/services/person.service.spec.ts index 0b3adec571..ec2417c793 100644 --- a/server/src/services/person.service.spec.ts +++ b/server/src/services/person.service.spec.ts @@ -3,10 +3,10 @@ import { BulkIdErrorReason } from 'src/dtos/asset-ids.response.dto'; import { mapFaces, mapPerson, PersonResponseDto } from 'src/dtos/person.dto'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { CacheControl, Colorspace, ImageFormat, SourceType, SystemMetadataKey } from 'src/enum'; -import { WithoutProperty } from 'src/interfaces/asset.interface'; import { JobName, JobStatus } from 'src/interfaces/job.interface'; import { DetectedFaces } from 'src/interfaces/machine-learning.interface'; -import { FaceSearchResult } from 'src/interfaces/search.interface'; +import { WithoutProperty } from 'src/repositories/asset.repository'; +import { FaceSearchResult } from 'src/repositories/search.repository'; import { PersonService } from 'src/services/person.service'; import { ImmichFileResponse } from 'src/utils/file'; import { assetStub } from 'test/fixtures/asset.stub'; diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index 116d2ec6c8..2ae703afb3 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -32,7 +32,6 @@ import { SourceType, SystemMetadataKey, } from 'src/enum'; -import { WithoutProperty } from 'src/interfaces/asset.interface'; import { JOBS_ASSET_PAGINATION_SIZE, JobItem, @@ -42,7 +41,8 @@ import { QueueName, } from 'src/interfaces/job.interface'; import { BoundingBox } from 'src/interfaces/machine-learning.interface'; -import { UpdateFacesData } from 'src/interfaces/person.interface'; +import { WithoutProperty } from 'src/repositories/asset.repository'; +import { UpdateFacesData } from 'src/repositories/person.repository'; import { BaseService } from 'src/services/base.service'; import { CropOptions, ImageDimensions, InputDimensions } from 'src/types'; import { getAssetFiles } from 'src/utils/asset.util'; diff --git a/server/src/services/search.service.ts b/server/src/services/search.service.ts index b74d3d3cba..e2ad9e7f99 100644 --- a/server/src/services/search.service.ts +++ b/server/src/services/search.service.ts @@ -16,7 +16,7 @@ import { } from 'src/dtos/search.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { AssetOrder } from 'src/enum'; -import { SearchExploreItem } from 'src/interfaces/search.interface'; +import { SearchExploreItem } from 'src/repositories/search.repository'; import { BaseService } from 'src/services/base.service'; import { getMyPartnerIds } from 'src/utils/asset.util'; import { isSmartSearchEnabled } from 'src/utils/misc'; @@ -109,7 +109,7 @@ export class SearchService extends BaseService { return suggestions; } - private getSuggestions(userIds: string[], dto: SearchSuggestionRequestDto) { + private getSuggestions(userIds: string[], dto: SearchSuggestionRequestDto): Promise> { switch (dto.type) { case SearchSuggestionType.COUNTRY: { return this.searchRepository.getCountries(userIds); @@ -127,7 +127,7 @@ export class SearchService extends BaseService { return this.searchRepository.getCameraModels(userIds, dto); } default: { - return [] as (string | null)[]; + return Promise.resolve([]); } } } diff --git a/server/src/services/server.service.ts b/server/src/services/server.service.ts index e9dd908a7c..9112c40a17 100644 --- a/server/src/services/server.service.ts +++ b/server/src/services/server.service.ts @@ -14,7 +14,7 @@ import { UsageByUserDto, } from 'src/dtos/server.dto'; import { StorageFolder, SystemMetadataKey } from 'src/enum'; -import { UserStatsQueryResponse } from 'src/interfaces/user.interface'; +import { UserStatsQueryResponse } from 'src/repositories/user.repository'; import { BaseService } from 'src/services/base.service'; import { asHumanReadable } from 'src/utils/bytes'; import { mimeTypes } from 'src/utils/mime-types'; diff --git a/server/src/services/smart-info.service.spec.ts b/server/src/services/smart-info.service.spec.ts index 79e13ea7ab..403c6e9a6a 100644 --- a/server/src/services/smart-info.service.spec.ts +++ b/server/src/services/smart-info.service.spec.ts @@ -1,7 +1,7 @@ import { SystemConfig } from 'src/config'; import { ImmichWorker } from 'src/enum'; -import { WithoutProperty } from 'src/interfaces/asset.interface'; import { JobName, JobStatus } from 'src/interfaces/job.interface'; +import { WithoutProperty } from 'src/repositories/asset.repository'; import { SmartInfoService } from 'src/services/smart-info.service'; import { getCLIPModelInfo } from 'src/utils/misc'; import { assetStub } from 'test/fixtures/asset.stub'; diff --git a/server/src/services/smart-info.service.ts b/server/src/services/smart-info.service.ts index 8fef961fe1..ebf9e1d287 100644 --- a/server/src/services/smart-info.service.ts +++ b/server/src/services/smart-info.service.ts @@ -2,10 +2,10 @@ import { Injectable } from '@nestjs/common'; import { SystemConfig } from 'src/config'; import { OnEvent, OnJob } from 'src/decorators'; import { ImmichWorker } from 'src/enum'; -import { WithoutProperty } from 'src/interfaces/asset.interface'; -import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; import { JOBS_ASSET_PAGINATION_SIZE, JobName, JobOf, JobStatus, QueueName } from 'src/interfaces/job.interface'; +import { WithoutProperty } from 'src/repositories/asset.repository'; +import { DatabaseLock } from 'src/repositories/database.repository'; import { BaseService } from 'src/services/base.service'; import { getAssetFiles } from 'src/utils/asset.util'; import { getCLIPModelInfo, isSmartSearchEnabled } from 'src/utils/misc'; diff --git a/server/src/services/storage-template.service.ts b/server/src/services/storage-template.service.ts index e8e4bd12a5..6b0409de1d 100644 --- a/server/src/services/storage-template.service.ts +++ b/server/src/services/storage-template.service.ts @@ -8,9 +8,9 @@ import { OnEvent, OnJob } from 'src/decorators'; import { SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { AssetPathType, AssetType, StorageFolder } from 'src/enum'; -import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; import { JobName, JobOf, JOBS_ASSET_PAGINATION_SIZE, JobStatus, QueueName } from 'src/interfaces/job.interface'; +import { DatabaseLock } from 'src/repositories/database.repository'; import { BaseService } from 'src/services/base.service'; import { getLivePhotoMotionFilename } from 'src/utils/file'; import { usePagination } from 'src/utils/pagination'; diff --git a/server/src/services/storage.service.ts b/server/src/services/storage.service.ts index ce26df4869..6921d33560 100644 --- a/server/src/services/storage.service.ts +++ b/server/src/services/storage.service.ts @@ -4,8 +4,8 @@ import { StorageCore } from 'src/cores/storage.core'; import { OnEvent, OnJob } from 'src/decorators'; import { SystemFlags } from 'src/entities/system-metadata.entity'; import { StorageFolder, SystemMetadataKey } from 'src/enum'; -import { DatabaseLock } from 'src/interfaces/database.interface'; import { JobName, JobOf, JobStatus, QueueName } from 'src/interfaces/job.interface'; +import { DatabaseLock } from 'src/repositories/database.repository'; import { BaseService } from 'src/services/base.service'; import { ImmichStartupError } from 'src/utils/misc'; diff --git a/server/src/services/tag.service.ts b/server/src/services/tag.service.ts index 4c31790d72..83d4b40340 100644 --- a/server/src/services/tag.service.ts +++ b/server/src/services/tag.service.ts @@ -14,7 +14,7 @@ import { import { TagEntity } from 'src/entities/tag.entity'; import { Permission } from 'src/enum'; import { JobName, JobStatus, QueueName } from 'src/interfaces/job.interface'; -import { AssetTagItem } from 'src/interfaces/tag.interface'; +import { AssetTagItem } from 'src/repositories/tag.repository'; import { BaseService } from 'src/services/base.service'; import { addAssets, removeAssets } from 'src/utils/asset.util'; import { upsertTags } from 'src/utils/tag'; diff --git a/server/src/services/timeline.service.spec.ts b/server/src/services/timeline.service.spec.ts index 15dab6bc05..749633998b 100644 --- a/server/src/services/timeline.service.spec.ts +++ b/server/src/services/timeline.service.spec.ts @@ -1,5 +1,5 @@ import { BadRequestException } from '@nestjs/common'; -import { TimeBucketSize } from 'src/interfaces/asset.interface'; +import { TimeBucketSize } from 'src/repositories/asset.repository'; import { TimelineService } from 'src/services/timeline.service'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; diff --git a/server/src/services/timeline.service.ts b/server/src/services/timeline.service.ts index 04fd206fe7..bc4c7fad73 100644 --- a/server/src/services/timeline.service.ts +++ b/server/src/services/timeline.service.ts @@ -3,7 +3,7 @@ import { AssetResponseDto, SanitizedAssetResponseDto, mapAsset } from 'src/dtos/ import { AuthDto } from 'src/dtos/auth.dto'; import { TimeBucketAssetDto, TimeBucketDto, TimeBucketResponseDto } from 'src/dtos/time-bucket.dto'; import { Permission } from 'src/enum'; -import { TimeBucketOptions } from 'src/interfaces/asset.interface'; +import { TimeBucketOptions } from 'src/repositories/asset.repository'; import { BaseService } from 'src/services/base.service'; import { getMyPartnerIds } from 'src/utils/asset.util'; diff --git a/server/src/services/user-admin.service.ts b/server/src/services/user-admin.service.ts index 784c95954e..3df9a218f3 100644 --- a/server/src/services/user-admin.service.ts +++ b/server/src/services/user-admin.service.ts @@ -12,7 +12,7 @@ import { } from 'src/dtos/user.dto'; import { UserMetadataKey, UserStatus } from 'src/enum'; import { JobName } from 'src/interfaces/job.interface'; -import { UserFindOptions } from 'src/interfaces/user.interface'; +import { UserFindOptions } from 'src/repositories/user.repository'; import { BaseService } from 'src/services/base.service'; import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences'; diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index f4ae42b5ed..dfd9c7715f 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -12,7 +12,7 @@ import { UserMetadataEntity } from 'src/entities/user-metadata.entity'; import { UserEntity } from 'src/entities/user.entity'; import { CacheControl, StorageFolder, UserMetadataKey } from 'src/enum'; import { JobName, JobOf, JobStatus, QueueName } from 'src/interfaces/job.interface'; -import { UserFindOptions } from 'src/interfaces/user.interface'; +import { UserFindOptions } from 'src/repositories/user.repository'; import { BaseService } from 'src/services/base.service'; import { ImmichFileResponse } from 'src/utils/file'; import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences'; diff --git a/server/src/services/version.service.ts b/server/src/services/version.service.ts index ff4fa3c6bf..ee36d30041 100644 --- a/server/src/services/version.service.ts +++ b/server/src/services/version.service.ts @@ -6,9 +6,9 @@ import { OnEvent, OnJob } 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 { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; import { JobName, JobStatus, QueueName } from 'src/interfaces/job.interface'; +import { DatabaseLock } from 'src/repositories/database.repository'; import { BaseService } from 'src/services/base.service'; const asNotification = ({ checkedAt, releaseVersion }: VersionCheckMetadata): ReleaseNotification => { diff --git a/server/src/utils/asset.util.ts b/server/src/utils/asset.util.ts index 39593a77f3..703fe2c3d7 100644 --- a/server/src/utils/asset.util.ts +++ b/server/src/utils/asset.util.ts @@ -5,12 +5,12 @@ import { UploadFieldName } from 'src/dtos/asset-media.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { AssetFileEntity } from 'src/entities/asset-files.entity'; import { AssetFileType, AssetType, Permission } from 'src/enum'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; -import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { AuthRequest } from 'src/middleware/auth.guard'; import { ImmichFile } from 'src/middleware/file-upload.interceptor'; import { AccessRepository } from 'src/repositories/access.repository'; +import { AssetRepository } from 'src/repositories/asset.repository'; +import { PartnerRepository } from 'src/repositories/partner.repository'; import { UploadFile } from 'src/services/asset-media.service'; import { checkAccess } from 'src/utils/access'; @@ -111,7 +111,7 @@ export const removeAssets = async ( export type PartnerIdOptions = { userId: string; - repository: IPartnerRepository; + repository: PartnerRepository; /** only include partners with `inTimeline: true` */ timelineEnabled?: boolean; }; @@ -139,7 +139,7 @@ export const getMyPartnerIds = async ({ userId, repository, timelineEnabled }: P return [...partnerIds]; }; -export type AssetHookRepositories = { asset: IAssetRepository; event: IEventRepository }; +export type AssetHookRepositories = { asset: AssetRepository; event: IEventRepository }; export const onBeforeLink = async ( { asset: assetRepository, event: eventRepository }: AssetHookRepositories, diff --git a/server/src/utils/config.ts b/server/src/utils/config.ts index 056064c026..30f965c37f 100644 --- a/server/src/utils/config.ts +++ b/server/src/utils/config.ts @@ -6,8 +6,8 @@ import * as _ from 'lodash'; import { SystemConfig, defaults } from 'src/config'; import { SystemConfigDto } from 'src/dtos/system-config.dto'; import { SystemMetadataKey } from 'src/enum'; -import { DatabaseLock } from 'src/interfaces/database.interface'; import { ConfigRepository } from 'src/repositories/config.repository'; +import { DatabaseLock } from 'src/repositories/database.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; import { DeepPartial } from 'src/types'; diff --git a/server/src/utils/file.ts b/server/src/utils/file.ts index d9c599169d..915c60de2a 100644 --- a/server/src/utils/file.ts +++ b/server/src/utils/file.ts @@ -4,8 +4,8 @@ import { access, constants } from 'node:fs/promises'; import { basename, extname, isAbsolute } from 'node:path'; import { promisify } from 'node:util'; import { CacheControl } from 'src/enum'; -import { ImmichReadStream } from 'src/interfaces/storage.interface'; import { LoggingRepository } from 'src/repositories/logging.repository'; +import { ImmichReadStream } from 'src/repositories/storage.repository'; import { isConnectionAborted } from 'src/utils/misc'; export function getFileNameWithoutExtension(path: string): string { diff --git a/server/src/utils/tag.ts b/server/src/utils/tag.ts index 027afcf040..4b3b360a8b 100644 --- a/server/src/utils/tag.ts +++ b/server/src/utils/tag.ts @@ -1,8 +1,8 @@ import { TagEntity } from 'src/entities/tag.entity'; -import { ITagRepository } from 'src/interfaces/tag.interface'; +import { TagRepository } from 'src/repositories/tag.repository'; type UpsertRequest = { userId: string; tags: string[] }; -export const upsertTags = async (repository: ITagRepository, { userId, tags }: UpsertRequest) => { +export const upsertTags = async (repository: TagRepository, { userId, tags }: UpsertRequest) => { tags = [...new Set(tags)]; const results: TagEntity[] = []; diff --git a/server/test/repositories/album.repository.mock.ts b/server/test/repositories/album.repository.mock.ts index dd5c3af6a8..7a1ae68a52 100644 --- a/server/test/repositories/album.repository.mock.ts +++ b/server/test/repositories/album.repository.mock.ts @@ -1,7 +1,8 @@ -import { IAlbumRepository } from 'src/interfaces/album.interface'; +import { AlbumRepository } from 'src/repositories/album.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newAlbumRepositoryMock = (): Mocked => { +export const newAlbumRepositoryMock = (): Mocked> => { return { getById: vitest.fn(), getByAssetId: vitest.fn(), diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index 928a7956c5..a2baed7cce 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -1,7 +1,8 @@ -import { IAssetRepository } from 'src/interfaces/asset.interface'; +import { AssetRepository } from 'src/repositories/asset.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newAssetRepositoryMock = (): Mocked => { +export const newAssetRepositoryMock = (): Mocked> => { return { create: vitest.fn(), upsertExif: vitest.fn(), diff --git a/server/test/repositories/config.repository.mock.ts b/server/test/repositories/config.repository.mock.ts index 800d40642b..7c5450c36e 100644 --- a/server/test/repositories/config.repository.mock.ts +++ b/server/test/repositories/config.repository.mock.ts @@ -1,5 +1,4 @@ -import { ImmichEnvironment, ImmichWorker } from 'src/enum'; -import { DatabaseExtension } from 'src/interfaces/database.interface'; +import { DatabaseExtension, ImmichEnvironment, ImmichWorker } from 'src/enum'; import { ConfigRepository, EnvData } from 'src/repositories/config.repository'; import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; diff --git a/server/test/repositories/crypto.repository.mock.ts b/server/test/repositories/crypto.repository.mock.ts index e0b6fa2bb8..9d32a88987 100644 --- a/server/test/repositories/crypto.repository.mock.ts +++ b/server/test/repositories/crypto.repository.mock.ts @@ -1,7 +1,8 @@ -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; +import { CryptoRepository } from 'src/repositories/crypto.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newCryptoRepositoryMock = (): Mocked => { +export const newCryptoRepositoryMock = (): Mocked> => { return { randomUUID: vitest.fn().mockReturnValue('random-uuid'), randomBytes: vitest.fn().mockReturnValue(Buffer.from('random-bytes', 'utf8')), diff --git a/server/test/repositories/database.repository.mock.ts b/server/test/repositories/database.repository.mock.ts index c135772518..fe954c725b 100644 --- a/server/test/repositories/database.repository.mock.ts +++ b/server/test/repositories/database.repository.mock.ts @@ -1,7 +1,8 @@ -import { IDatabaseRepository } from 'src/interfaces/database.interface'; +import { DatabaseRepository } from 'src/repositories/database.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newDatabaseRepositoryMock = (): Mocked => { +export const newDatabaseRepositoryMock = (): Mocked> => { return { init: vitest.fn(), shutdown: vitest.fn(), diff --git a/server/test/repositories/library.repository.mock.ts b/server/test/repositories/library.repository.mock.ts index 83e97c7ffa..5db9e18d33 100644 --- a/server/test/repositories/library.repository.mock.ts +++ b/server/test/repositories/library.repository.mock.ts @@ -1,7 +1,8 @@ -import { ILibraryRepository } from 'src/interfaces/library.interface'; +import { LibraryRepository } from 'src/repositories/library.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newLibraryRepositoryMock = (): Mocked => { +export const newLibraryRepositoryMock = (): Mocked> => { return { get: vitest.fn(), create: vitest.fn(), diff --git a/server/test/repositories/move.repository.mock.ts b/server/test/repositories/move.repository.mock.ts index 1f982a048d..cf304b591e 100644 --- a/server/test/repositories/move.repository.mock.ts +++ b/server/test/repositories/move.repository.mock.ts @@ -1,7 +1,8 @@ -import { IMoveRepository } from 'src/interfaces/move.interface'; +import { MoveRepository } from 'src/repositories/move.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newMoveRepositoryMock = (): Mocked => { +export const newMoveRepositoryMock = (): Mocked> => { return { create: vitest.fn(), getByEntity: vitest.fn(), diff --git a/server/test/repositories/partner.repository.mock.ts b/server/test/repositories/partner.repository.mock.ts index ec1f141075..6f9d4a36be 100644 --- a/server/test/repositories/partner.repository.mock.ts +++ b/server/test/repositories/partner.repository.mock.ts @@ -1,7 +1,8 @@ -import { IPartnerRepository } from 'src/interfaces/partner.interface'; +import { PartnerRepository } from 'src/repositories/partner.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newPartnerRepositoryMock = (): Mocked => { +export const newPartnerRepositoryMock = (): Mocked> => { return { create: vitest.fn(), remove: vitest.fn(), diff --git a/server/test/repositories/person.repository.mock.ts b/server/test/repositories/person.repository.mock.ts index d7b92d3eab..52240aafa2 100644 --- a/server/test/repositories/person.repository.mock.ts +++ b/server/test/repositories/person.repository.mock.ts @@ -1,7 +1,8 @@ -import { IPersonRepository } from 'src/interfaces/person.interface'; +import { PersonRepository } from 'src/repositories/person.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newPersonRepositoryMock = (): Mocked => { +export const newPersonRepositoryMock = (): Mocked> => { return { getById: vitest.fn(), getAll: vitest.fn(), diff --git a/server/test/repositories/search.repository.mock.ts b/server/test/repositories/search.repository.mock.ts index be0e753e30..520bf23b3e 100644 --- a/server/test/repositories/search.repository.mock.ts +++ b/server/test/repositories/search.repository.mock.ts @@ -1,7 +1,8 @@ -import { ISearchRepository } from 'src/interfaces/search.interface'; +import { SearchRepository } from 'src/repositories/search.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newSearchRepositoryMock = (): Mocked => { +export const newSearchRepositoryMock = (): Mocked> => { return { searchMetadata: vitest.fn(), searchSmart: vitest.fn(), diff --git a/server/test/repositories/shared-link.repository.mock.ts b/server/test/repositories/shared-link.repository.mock.ts index 251b38d5d7..66044b9eed 100644 --- a/server/test/repositories/shared-link.repository.mock.ts +++ b/server/test/repositories/shared-link.repository.mock.ts @@ -1,7 +1,8 @@ -import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; +import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newSharedLinkRepositoryMock = (): Mocked => { +export const newSharedLinkRepositoryMock = (): Mocked> => { return { getAll: vitest.fn(), get: vitest.fn(), diff --git a/server/test/repositories/stack.repository.mock.ts b/server/test/repositories/stack.repository.mock.ts index 35d1614de7..74fef6f4b1 100644 --- a/server/test/repositories/stack.repository.mock.ts +++ b/server/test/repositories/stack.repository.mock.ts @@ -1,7 +1,8 @@ -import { IStackRepository } from 'src/interfaces/stack.interface'; +import { StackRepository } from 'src/repositories/stack.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newStackRepositoryMock = (): Mocked => { +export const newStackRepositoryMock = (): Mocked> => { return { search: vitest.fn(), create: vitest.fn(), diff --git a/server/test/repositories/storage.repository.mock.ts b/server/test/repositories/storage.repository.mock.ts index 0af16a8d17..984785d510 100644 --- a/server/test/repositories/storage.repository.mock.ts +++ b/server/test/repositories/storage.repository.mock.ts @@ -1,6 +1,7 @@ import { WatchOptions } from 'chokidar'; import { StorageCore } from 'src/cores/storage.core'; -import { IStorageRepository, WatchEvents } from 'src/interfaces/storage.interface'; +import { StorageRepository, WatchEvents } from 'src/repositories/storage.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; interface MockWatcherOptions { @@ -39,7 +40,7 @@ export const makeMockWatcher = return () => Promise.resolve(); }; -export const newStorageRepositoryMock = (reset = true): Mocked => { +export const newStorageRepositoryMock = (reset = true): Mocked> => { if (reset) { StorageCore.reset(); } diff --git a/server/test/repositories/tag.repository.mock.ts b/server/test/repositories/tag.repository.mock.ts index acc2b59f6d..7f6f1b6c53 100644 --- a/server/test/repositories/tag.repository.mock.ts +++ b/server/test/repositories/tag.repository.mock.ts @@ -1,7 +1,8 @@ -import { ITagRepository } from 'src/interfaces/tag.interface'; +import { TagRepository } from 'src/repositories/tag.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newTagRepositoryMock = (): Mocked => { +export const newTagRepositoryMock = (): Mocked> => { return { getAll: vitest.fn(), getByValue: vitest.fn(), diff --git a/server/test/repositories/user.repository.mock.ts b/server/test/repositories/user.repository.mock.ts index e6e8c38184..d7ebee09d8 100644 --- a/server/test/repositories/user.repository.mock.ts +++ b/server/test/repositories/user.repository.mock.ts @@ -1,7 +1,8 @@ -import { IUserRepository } from 'src/interfaces/user.interface'; +import { UserRepository } from 'src/repositories/user.repository'; +import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newUserRepositoryMock = (): Mocked => { +export const newUserRepositoryMock = (): Mocked> => { return { get: vitest.fn(), getAdmin: vitest.fn(), diff --git a/server/test/utils.ts b/server/test/utils.ts index c4fee8fe93..9816266a21 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -2,16 +2,14 @@ import { ChildProcessWithoutNullStreams } from 'node:child_process'; import { Writable } from 'node:stream'; import { PNG } from 'pngjs'; import { ImmichWorker } from 'src/enum'; -import { IAlbumRepository } from 'src/interfaces/album.interface'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; import { AccessRepository } from 'src/repositories/access.repository'; import { ActivityRepository } from 'src/repositories/activity.repository'; import { AlbumUserRepository } from 'src/repositories/album-user.repository'; +import { AlbumRepository } from 'src/repositories/album.repository'; import { ApiKeyRepository } from 'src/repositories/api-key.repository'; +import { AssetRepository } from 'src/repositories/asset.repository'; import { AuditRepository } from 'src/repositories/audit.repository'; import { ConfigRepository } from 'src/repositories/config.repository'; import { CronRepository } from 'src/repositories/cron.repository'; @@ -40,6 +38,7 @@ import { SystemMetadataRepository } from 'src/repositories/system-metadata.repos import { TagRepository } from 'src/repositories/tag.repository'; import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TrashRepository } from 'src/repositories/trash.repository'; +import { UserRepository } from 'src/repositories/user.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; import { ViewRepository } from 'src/repositories/view-repository'; import { BaseService } from 'src/services/base.service'; @@ -100,14 +99,14 @@ type IAccessRepository = { [K in keyof AccessRepository]: RepositoryInterface>; - album: Mocked; + album: Mocked>; albumUser: Mocked>; apiKey: Mocked>; audit: Mocked>; - asset: Mocked; + asset: Mocked>; config: Mocked>; cron: Mocked>; - crypto: Mocked; + crypto: Mocked>; database: Mocked>; event: Mocked; job: Mocked>; @@ -134,7 +133,7 @@ export type ServiceMocks = { tag: Mocked>; telemetry: ITelemetryRepositoryMock; trash: Mocked>; - user: Mocked; + user: Mocked>; versionHistory: Mocked>; view: Mocked>; }; @@ -192,39 +191,39 @@ export const newTestService = ( accessMock as IAccessRepository as AccessRepository, activityMock as RepositoryInterface as ActivityRepository, auditMock as RepositoryInterface as AuditRepository, - albumMock, + albumMock as RepositoryInterface as AlbumRepository, albumUserMock as RepositoryInterface as AlbumUserRepository, - assetMock, + assetMock as RepositoryInterface as AssetRepository, configMock, cronMock as RepositoryInterface as CronRepository, cryptoMock as RepositoryInterface as CryptoRepository, - databaseMock, + databaseMock as RepositoryInterface as DatabaseRepository, eventMock, jobMock, apiKeyMock as RepositoryInterface as ApiKeyRepository, - libraryMock, + libraryMock as RepositoryInterface as LibraryRepository, machineLearningMock, mapMock as RepositoryInterface as MapRepository, mediaMock as RepositoryInterface as MediaRepository, memoryMock as RepositoryInterface as MemoryRepository, metadataMock as RepositoryInterface as MetadataRepository, - moveMock, + moveMock as RepositoryInterface as MoveRepository, notificationMock as RepositoryInterface as NotificationRepository, oauthMock as RepositoryInterface as OAuthRepository, - partnerMock, - personMock, + partnerMock as RepositoryInterface as PartnerRepository, + personMock as RepositoryInterface as PersonRepository, processMock as RepositoryInterface as ProcessRepository, - searchMock, + searchMock as RepositoryInterface as SearchRepository, serverInfoMock as RepositoryInterface as ServerInfoRepository, sessionMock as RepositoryInterface as SessionRepository, - sharedLinkMock, - stackMock, - storageMock, + sharedLinkMock as RepositoryInterface as SharedLinkRepository, + stackMock as RepositoryInterface as StackRepository, + storageMock as RepositoryInterface as StorageRepository, systemMock as RepositoryInterface as SystemMetadataRepository, - tagMock, + tagMock as RepositoryInterface as TagRepository, telemetryMock as unknown as TelemetryRepository, trashMock as RepositoryInterface as TrashRepository, - userMock, + userMock as RepositoryInterface as UserRepository, versionHistoryMock as RepositoryInterface as VersionHistoryRepository, viewMock as RepositoryInterface as ViewRepository, );