mirror of
https://github.com/immich-app/immich.git
synced 2025-04-21 07:26:25 +02:00
refactor: repositories (#16036)
This commit is contained in:
parent
d2575d8f00
commit
9d85272c2b
90 changed files with 686 additions and 1088 deletions
server
src
cores
dtos
entities
enum.tsinterfaces
album.interface.tsasset.interface.tscrypto.interface.tsdatabase.interface.tslibrary.interface.tsmove.interface.tspartner.interface.tsperson.interface.tssearch.interface.tsshared-link.interface.tsstack.interface.tsstorage.interface.tstag.interface.tsuser.interface.ts
migrations
1700713994428-AddCLIPEmbeddingIndex.ts1700714033632-AddFaceEmbeddingIndex.ts1718486162779-AddFaceSearchRelation.ts
repositories
album.repository.tsasset.repository.tsconfig.repository.tscrypto.repository.tsdatabase.repository.tsindex.tslibrary.repository.tsmove.repository.tspartner.repository.tsperson.repository.tssearch.repository.tsshared-link.repository.tsstack.repository.tsstorage.repository.tstag.repository.tsuser.repository.ts
services
album.service.tsasset.service.spec.tsbackup.service.tsbase.service.tsdatabase.service.spec.tsdatabase.service.tsdownload.service.tsduplicate.service.spec.tsduplicate.service.tslibrary.service.tsmedia.service.spec.tsmedia.service.tsmetadata.service.spec.tsmetadata.service.tspartner.service.spec.tspartner.service.tsperson.service.spec.tsperson.service.tssearch.service.tsserver.service.tssmart-info.service.spec.tssmart-info.service.tsstorage-template.service.tsstorage.service.tstag.service.tstimeline.service.spec.tstimeline.service.tsuser-admin.service.tsuser.service.tsversion.service.ts
utils
test
repositories
album.repository.mock.tsasset.repository.mock.tsconfig.repository.mock.tscrypto.repository.mock.tsdatabase.repository.mock.tslibrary.repository.mock.tsmove.repository.mock.tspartner.repository.mock.tsperson.repository.mock.tssearch.repository.mock.tsshared-link.repository.mock.tsstack.repository.mock.tsstorage.repository.mock.tstag.repository.mock.tsuser.repository.mock.ts
utils.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', () => {
|
||||
|
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -384,3 +384,10 @@ export enum ExifOrientation {
|
|||
MirrorHorizontalRotate90CW = 7,
|
||||
Rotate270CW = 8,
|
||||
}
|
||||
|
||||
export enum DatabaseExtension {
|
||||
CUBE = 'cube',
|
||||
EARTH_DISTANCE = 'earthdistance',
|
||||
VECTOR = 'vector',
|
||||
VECTORS = 'vectors',
|
||||
}
|
||||
|
|
|
@ -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<AlbumEntity | undefined>;
|
||||
getByAssetId(ownerId: string, assetId: string): Promise<AlbumEntity[]>;
|
||||
removeAsset(assetId: string): Promise<void>;
|
||||
getMetadataForIds(ids: string[]): Promise<AlbumAssetCount[]>;
|
||||
getOwned(ownerId: string): Promise<AlbumEntity[]>;
|
||||
getShared(ownerId: string): Promise<AlbumEntity[]>;
|
||||
getNotShared(ownerId: string): Promise<AlbumEntity[]>;
|
||||
restoreAll(userId: string): Promise<void>;
|
||||
softDeleteAll(userId: string): Promise<void>;
|
||||
deleteAll(userId: string): Promise<void>;
|
||||
create(album: Insertable<Albums>, assetIds: string[], albumUsers: AlbumUserCreateDto[]): Promise<AlbumEntity>;
|
||||
update(id: string, album: Updateable<Albums>): Promise<AlbumEntity>;
|
||||
delete(id: string): Promise<void>;
|
||||
updateThumbnails(): Promise<number | undefined>;
|
||||
}
|
|
@ -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<AssetType, number>;
|
||||
|
||||
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<AssetEntity, 'id' | 'originalPath' | 'isOffline'>;
|
||||
|
||||
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<Assets>): Promise<AssetEntity>;
|
||||
getByIds(ids: string[], relations?: GetByIdsRelations): Promise<AssetEntity[]>;
|
||||
getByIdsWithAllRelations(ids: string[]): Promise<AssetEntity[]>;
|
||||
getByDayOfYear(ownerIds: string[], monthDay: MonthDay): Promise<DayOfYearAssets[]>;
|
||||
getByChecksum(options: AssetGetByChecksumOptions): Promise<AssetEntity | undefined>;
|
||||
getByChecksums(userId: string, checksums: Buffer[]): Promise<AssetEntity[]>;
|
||||
getUploadAssetIdByChecksum(ownerId: string, checksum: Buffer): Promise<string | undefined>;
|
||||
getByAlbumId(pagination: PaginationOptions, albumId: string): Paginated<AssetEntity>;
|
||||
getByDeviceIds(ownerId: string, deviceId: string, deviceAssetIds: string[]): Promise<string[]>;
|
||||
getByUserId(pagination: PaginationOptions, userId: string, options?: AssetSearchOptions): Paginated<AssetEntity>;
|
||||
getById(id: string, relations?: GetByIdsRelations): Promise<AssetEntity | undefined>;
|
||||
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity>;
|
||||
getRandom(userIds: string[], count: number): Promise<AssetEntity[]>;
|
||||
getLastUpdatedAssetForAlbumId(albumId: string): Promise<AssetEntity | undefined>;
|
||||
getByLibraryIdAndOriginalPath(libraryId: string, originalPath: string): Promise<AssetEntity | undefined>;
|
||||
deleteAll(ownerId: string): Promise<void>;
|
||||
getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated<AssetEntity>;
|
||||
getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>;
|
||||
getLivePhotoCount(motionId: string): Promise<number>;
|
||||
updateAll(ids: string[], options: Updateable<Assets>): Promise<void>;
|
||||
updateDuplicates(options: AssetUpdateDuplicateOptions): Promise<void>;
|
||||
update(asset: Updateable<Assets> & { id: string }): Promise<AssetEntity>;
|
||||
remove(asset: AssetEntity): Promise<void>;
|
||||
findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | undefined>;
|
||||
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
|
||||
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
|
||||
getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
|
||||
upsertExif(exif: Insertable<Exif>): Promise<void>;
|
||||
upsertJobStatus(...jobStatus: Insertable<AssetJobStatus>[]): Promise<void>;
|
||||
getAssetIdByCity(userId: string, options: AssetExploreFieldOptions): Promise<SearchExploreItem<string>>;
|
||||
getDuplicates(userId: string): Promise<DuplicateGroup[]>;
|
||||
getAllForUserFullSync(options: AssetFullSyncOptions): Promise<AssetEntity[]>;
|
||||
getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise<AssetEntity[]>;
|
||||
upsertFile(options: Insertable<AssetFiles>): Promise<void>;
|
||||
upsertFiles(options: Insertable<AssetFiles>[]): Promise<void>;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
export const ICryptoRepository = 'ICryptoRepository';
|
||||
|
||||
export interface ICryptoRepository {
|
||||
randomBytes(size: number): Buffer;
|
||||
randomUUID(): string;
|
||||
hashFile(filePath: string | Buffer): Promise<Buffer>;
|
||||
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<string>;
|
||||
compareBcrypt(data: string | Buffer, encrypted: string): boolean;
|
||||
newPassword(bytes: number): string;
|
||||
}
|
|
@ -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<DatabaseExtension, string> = {
|
||||
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<boolean>;
|
||||
shutdown(): Promise<void>;
|
||||
getExtensionVersion(extension: DatabaseExtension): Promise<ExtensionVersion>;
|
||||
getExtensionVersionRange(extension: VectorExtension): string;
|
||||
getPostgresVersion(): Promise<string>;
|
||||
getPostgresVersionRange(): string;
|
||||
createExtension(extension: DatabaseExtension): Promise<void>;
|
||||
updateVectorExtension(extension: VectorExtension, version?: string): Promise<VectorUpdateResult>;
|
||||
reindex(index: VectorIndex): Promise<void>;
|
||||
shouldReindex(name: VectorIndex): Promise<boolean>;
|
||||
runMigrations(options?: { transaction?: 'all' | 'none' | 'each' }): Promise<void>;
|
||||
withLock<R>(lock: DatabaseLock, callback: () => Promise<R>): Promise<R>;
|
||||
tryLock(lock: DatabaseLock): Promise<boolean>;
|
||||
isBusy(lock: DatabaseLock): boolean;
|
||||
wait(lock: DatabaseLock): Promise<void>;
|
||||
}
|
|
@ -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<LibraryEntity[]>;
|
||||
getAllDeleted(): Promise<LibraryEntity[]>;
|
||||
get(id: string, withDeleted?: boolean): Promise<LibraryEntity | undefined>;
|
||||
create(library: Insertable<Libraries>): Promise<LibraryEntity>;
|
||||
delete(id: string): Promise<void>;
|
||||
softDelete(id: string): Promise<void>;
|
||||
update(id: string, library: Updateable<Libraries>): Promise<LibraryEntity>;
|
||||
getStatistics(id: string): Promise<LibraryStatsResponseDto | undefined>;
|
||||
}
|
|
@ -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<MoveEntity, 'oldPath' | 'newPath' | 'entityId' | 'pathType'> & Partial<MoveEntity>;
|
||||
|
||||
export interface IMoveRepository {
|
||||
create(entity: Insertable<MoveHistory>): Promise<MoveEntity>;
|
||||
getByEntity(entityId: string, pathType: PathType): Promise<MoveEntity | undefined>;
|
||||
update(id: string, entity: Updateable<MoveHistory>): Promise<MoveEntity>;
|
||||
delete(id: string): Promise<MoveEntity>;
|
||||
}
|
|
@ -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<PartnerEntity[]>;
|
||||
get(partner: PartnerIds): Promise<PartnerEntity | undefined>;
|
||||
create(partner: PartnerIds): Promise<PartnerEntity>;
|
||||
remove(partner: PartnerIds): Promise<void>;
|
||||
update(partner: PartnerIds, entity: Updateable<Partners>): Promise<PartnerEntity>;
|
||||
}
|
|
@ -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<AssetFaces>)[];
|
||||
|
||||
export interface IPersonRepository {
|
||||
getAll(options?: Partial<PersonEntity>): AsyncIterableIterator<PersonEntity>;
|
||||
getAllForUser(pagination: PaginationOptions, userId: string, options: PersonSearchOptions): Paginated<PersonEntity>;
|
||||
getAllWithoutFaces(): Promise<PersonEntity[]>;
|
||||
getById(personId: string): Promise<PersonEntity | null>;
|
||||
getByName(userId: string, personName: string, options: PersonNameSearchOptions): Promise<PersonEntity[]>;
|
||||
getDistinctNames(userId: string, options: PersonNameSearchOptions): Promise<PersonNameResponse[]>;
|
||||
|
||||
create(person: Insertable<Person>): Promise<PersonEntity>;
|
||||
createAll(people: Insertable<Person>[]): Promise<string[]>;
|
||||
delete(entities: PersonEntity[]): Promise<void>;
|
||||
deleteFaces(options: DeleteFacesOptions): Promise<void>;
|
||||
refreshFaces(
|
||||
facesToAdd: Insertable<AssetFaces>[],
|
||||
faceIdsToRemove: string[],
|
||||
embeddingsToAdd?: Insertable<FaceSearch>[],
|
||||
): Promise<void>;
|
||||
getAllFaces(options?: Partial<AssetFaceEntity>): AsyncIterableIterator<AssetFaceEntity>;
|
||||
getFaceById(id: string): Promise<AssetFaceEntity>;
|
||||
getFaceByIdWithAssets(
|
||||
id: string,
|
||||
relations?: FindOptionsRelations<AssetFaceEntity>,
|
||||
select?: SelectFaceOptions,
|
||||
): Promise<AssetFaceEntity | undefined>;
|
||||
getFaces(assetId: string): Promise<AssetFaceEntity[]>;
|
||||
getFacesByIds(ids: AssetFaceId[]): Promise<AssetFaceEntity[]>;
|
||||
getRandomFace(personId: string): Promise<AssetFaceEntity | undefined>;
|
||||
getStatistics(personId: string): Promise<PersonStatistics>;
|
||||
reassignFace(assetFaceId: string, newPersonId: string): Promise<number>;
|
||||
getNumberOfPeople(userId: string): Promise<PeopleStatistics>;
|
||||
reassignFaces(data: UpdateFacesData): Promise<number>;
|
||||
unassignFaces(options: UnassignFacesOptions): Promise<void>;
|
||||
update(person: Updateable<Person> & { id: string }): Promise<PersonEntity>;
|
||||
updateAll(people: Insertable<Person>[]): Promise<void>;
|
||||
getLatestFaceDate(): Promise<string | undefined>;
|
||||
}
|
|
@ -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<T> {
|
||||
/** 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<T> = Array<{
|
||||
value: string;
|
||||
data: T;
|
||||
}>;
|
||||
|
||||
export interface SearchExploreItem<T> {
|
||||
fieldName: string;
|
||||
items: SearchExploreItemSet<T>;
|
||||
}
|
||||
|
||||
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<AssetSearchOptions, 'orderDirection'>;
|
||||
|
||||
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<AssetEntity>;
|
||||
searchSmart(pagination: SearchPaginationOptions, options: SmartSearchOptions): Paginated<AssetEntity>;
|
||||
searchDuplicates(options: AssetDuplicateSearch): Promise<AssetDuplicateResult[]>;
|
||||
searchFaces(search: FaceEmbeddingSearch): Promise<FaceSearchResult[]>;
|
||||
searchRandom(size: number, options: AssetSearchOptions): Promise<AssetEntity[]>;
|
||||
upsert(assetId: string, embedding: string): Promise<void>;
|
||||
searchPlaces(placeName: string): Promise<GeodataPlacesEntity[]>;
|
||||
getAssetsByCity(userIds: string[]): Promise<AssetEntity[]>;
|
||||
deleteAllSearchEmbeddings(): Promise<void>;
|
||||
getDimensionSize(): Promise<number>;
|
||||
setDimensionSize(dimSize: number): Promise<void>;
|
||||
getCountries(userIds: string[]): Promise<Array<string | null>>;
|
||||
getStates(userIds: string[], options: GetStatesOptions): Promise<Array<string | null>>;
|
||||
getCities(userIds: string[], options: GetCitiesOptions): Promise<Array<string | null>>;
|
||||
getCameraMakes(userIds: string[], options: GetCameraMakesOptions): Promise<Array<string | null>>;
|
||||
getCameraModels(userIds: string[], options: GetCameraModelsOptions): Promise<Array<string | 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<SharedLinkEntity[]>;
|
||||
get(userId: string, id: string): Promise<SharedLinkEntity | undefined>;
|
||||
getByKey(key: Buffer): Promise<SharedLinkEntity | undefined>;
|
||||
create(entity: Insertable<SharedLinks> & { assetIds?: string[] }): Promise<SharedLinkEntity>;
|
||||
update(entity: Updateable<SharedLinks> & { id: string; assetIds?: string[] }): Promise<SharedLinkEntity>;
|
||||
remove(entity: SharedLinkEntity): Promise<void>;
|
||||
}
|
|
@ -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<StackEntity[]>;
|
||||
create(stack: { ownerId: string; assetIds: string[] }): Promise<StackEntity>;
|
||||
update(id: string, entity: Updateable<StackEntity>): Promise<StackEntity>;
|
||||
delete(id: string): Promise<void>;
|
||||
deleteAll(ids: string[]): Promise<void>;
|
||||
getById(id: string): Promise<StackEntity | undefined>;
|
||||
}
|
|
@ -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<void>;
|
||||
}
|
||||
|
||||
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<ImmichReadStream>;
|
||||
readFile(filepath: string, options?: FileReadOptions<Buffer>): Promise<Buffer>;
|
||||
createFile(filepath: string, buffer: Buffer): Promise<void>;
|
||||
createWriteStream(filepath: string): Writable;
|
||||
createOrOverwriteFile(filepath: string, buffer: Buffer): Promise<void>;
|
||||
overwriteFile(filepath: string, buffer: Buffer): Promise<void>;
|
||||
realpath(filepath: string): Promise<string>;
|
||||
unlink(filepath: string): Promise<void>;
|
||||
unlinkDir(folder: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>;
|
||||
removeEmptyDirs(folder: string, self?: boolean): Promise<void>;
|
||||
checkFileExists(filepath: string, mode?: number): Promise<boolean>;
|
||||
mkdirSync(filepath: string): void;
|
||||
checkDiskUsage(folder: string): Promise<DiskUsage>;
|
||||
readdir(folder: string): Promise<string[]>;
|
||||
stat(filepath: string): Promise<Stats>;
|
||||
crawl(options: CrawlOptionsDto): Promise<string[]>;
|
||||
walk(options: WalkOptionsDto): AsyncGenerator<string[]>;
|
||||
copyFile(source: string, target: string): Promise<void>;
|
||||
rename(source: string, target: string): Promise<void>;
|
||||
watch(paths: string[], options: WatchOptions, events: Partial<WatchEvents>): () => Promise<void>;
|
||||
utimes(filepath: string, atime: Date, mtime: Date): Promise<void>;
|
||||
}
|
|
@ -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<TagEntity[]>;
|
||||
getByValue(userId: string, value: string): Promise<TagEntity | null>;
|
||||
upsertValue(request: { userId: string; value: string; parent?: TagEntity }): Promise<TagEntity>;
|
||||
|
||||
create(tag: Partial<TagEntity>): Promise<TagEntity>;
|
||||
get(id: string): Promise<TagEntity | null>;
|
||||
update(tag: { id: string } & Partial<TagEntity>): Promise<TagEntity>;
|
||||
delete(id: string): Promise<void>;
|
||||
|
||||
upsertAssetTags({ assetId, tagIds }: { assetId: string; tagIds: string[] }): Promise<void>;
|
||||
upsertAssetIds(items: AssetTagItem[]): Promise<AssetTagItem[]>;
|
||||
deleteEmptyTags(): Promise<void>;
|
||||
}
|
|
@ -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<UserEntity | undefined>;
|
||||
getAdmin(): Promise<UserEntity | undefined>;
|
||||
hasAdmin(): Promise<boolean>;
|
||||
getByEmail(email: string, withPassword?: boolean): Promise<UserEntity | undefined>;
|
||||
getByStorageLabel(storageLabel: string): Promise<UserEntity | undefined>;
|
||||
getByOAuthId(oauthId: string): Promise<UserEntity | undefined>;
|
||||
getDeletedUsers(): Promise<UserEntity[]>;
|
||||
getList(filter?: UserListFilter): Promise<UserEntity[]>;
|
||||
getUserStats(): Promise<UserStatsQueryResponse[]>;
|
||||
create(user: Insertable<Users>): Promise<UserEntity>;
|
||||
update(id: string, user: Updateable<Users>): Promise<UserEntity>;
|
||||
restore(id: string): Promise<UserEntity>;
|
||||
upsertMetadata<T extends keyof UserMetadata>(id: string, item: { key: T; value: UserMetadata[T] }): Promise<void>;
|
||||
deleteMetadata<T extends keyof UserMetadata>(id: string, key: T): Promise<void>;
|
||||
delete(user: UserEntity, hard?: boolean): Promise<UserEntity>;
|
||||
updateUsage(id: string, delta: number): Promise<void>;
|
||||
syncUsage(id?: string): Promise<void>;
|
||||
}
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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<DB, 'albums'>) => {
|
|||
};
|
||||
|
||||
@Injectable()
|
||||
export class AlbumRepository implements IAlbumRepository {
|
||||
export class AlbumRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, { withAssets: true }] })
|
||||
|
|
|
@ -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<AssetType, number>;
|
||||
|
||||
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<AssetEntity, 'id' | 'originalPath' | 'isOffline'>;
|
||||
|
||||
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<DB>) {}
|
||||
|
||||
async upsertExif(exif: Insertable<Exif>): Promise<void> {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<DatabaseExtension, string> = {
|
||||
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();
|
||||
|
||||
|
|
|
@ -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 },
|
||||
];
|
||||
|
|
|
@ -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<DB, 'libraries'>) => {
|
|||
};
|
||||
|
||||
@Injectable()
|
||||
export class LibraryRepository implements ILibraryRepository {
|
||||
export class LibraryRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
|
|
|
@ -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<MoveEntity, 'oldPath' | 'newPath' | 'entityId' | 'pathType'> & Partial<MoveEntity>;
|
||||
|
||||
@Injectable()
|
||||
export class MoveRepository implements IMoveRepository {
|
||||
export class MoveRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
create(entity: Insertable<MoveHistory>): Promise<MoveEntity> {
|
||||
|
|
|
@ -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<DB, 'partners'>) => {
|
|||
};
|
||||
|
||||
@Injectable()
|
||||
export class PartnerRepository implements IPartnerRepository {
|
||||
export class PartnerRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
|
|
|
@ -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<AssetFaces>)[];
|
||||
|
||||
const withPerson = (eb: ExpressionBuilder<DB, 'asset_faces'>) => {
|
||||
return jsonObjectFrom(
|
||||
eb.selectFrom('person').selectAll('person').whereRef('person.id', '=', 'asset_faces.personId'),
|
||||
|
@ -43,7 +73,7 @@ const withFaceSearch = (eb: ExpressionBuilder<DB, 'asset_faces'>) => {
|
|||
};
|
||||
|
||||
@Injectable()
|
||||
export class PersonRepository implements IPersonRepository {
|
||||
export class PersonRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [{ oldPersonId: DummyValue.UUID, newPersonId: DummyValue.UUID }] })
|
||||
|
|
|
@ -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<T> {
|
||||
/** 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<T> = Array<{
|
||||
value: string;
|
||||
data: T;
|
||||
}>;
|
||||
|
||||
export interface SearchExploreItem<T> {
|
||||
fieldName: string;
|
||||
items: SearchExploreItemSet<T>;
|
||||
}
|
||||
|
||||
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<AssetSearchOptions, 'orderDirection'>;
|
||||
|
||||
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<DB>,
|
||||
|
|
|
@ -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<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
||||
|
|
|
@ -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<DB, 'asset_stack'>, withTags = false) => {
|
||||
return jsonArrayFrom(
|
||||
eb
|
||||
|
@ -35,7 +39,7 @@ const withAssets = (eb: ExpressionBuilder<DB, 'asset_stack'>, withTags = false)
|
|||
};
|
||||
|
||||
@Injectable()
|
||||
export class StackRepository implements IStackRepository {
|
||||
export class StackRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [{ ownerId: DummyValue.UUID }] })
|
||||
|
|
|
@ -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<void>;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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<TagEntity>,
|
||||
|
|
|
@ -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<DbUserMetadata>;
|
||||
|
||||
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<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.BOOLEAN] })
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<Array<string | null>> {
|
||||
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([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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[] = [];
|
||||
|
|
|
@ -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<IAlbumRepository> => {
|
||||
export const newAlbumRepositoryMock = (): Mocked<RepositoryInterface<AlbumRepository>> => {
|
||||
return {
|
||||
getById: vitest.fn(),
|
||||
getByAssetId: vitest.fn(),
|
||||
|
|
|
@ -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<IAssetRepository> => {
|
||||
export const newAssetRepositoryMock = (): Mocked<RepositoryInterface<AssetRepository>> => {
|
||||
return {
|
||||
create: vitest.fn(),
|
||||
upsertExif: vitest.fn(),
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<ICryptoRepository> => {
|
||||
export const newCryptoRepositoryMock = (): Mocked<RepositoryInterface<CryptoRepository>> => {
|
||||
return {
|
||||
randomUUID: vitest.fn().mockReturnValue('random-uuid'),
|
||||
randomBytes: vitest.fn().mockReturnValue(Buffer.from('random-bytes', 'utf8')),
|
||||
|
|
|
@ -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<IDatabaseRepository> => {
|
||||
export const newDatabaseRepositoryMock = (): Mocked<RepositoryInterface<DatabaseRepository>> => {
|
||||
return {
|
||||
init: vitest.fn(),
|
||||
shutdown: vitest.fn(),
|
||||
|
|
|
@ -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<ILibraryRepository> => {
|
||||
export const newLibraryRepositoryMock = (): Mocked<RepositoryInterface<LibraryRepository>> => {
|
||||
return {
|
||||
get: vitest.fn(),
|
||||
create: vitest.fn(),
|
||||
|
|
|
@ -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<IMoveRepository> => {
|
||||
export const newMoveRepositoryMock = (): Mocked<RepositoryInterface<MoveRepository>> => {
|
||||
return {
|
||||
create: vitest.fn(),
|
||||
getByEntity: vitest.fn(),
|
||||
|
|
|
@ -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<IPartnerRepository> => {
|
||||
export const newPartnerRepositoryMock = (): Mocked<RepositoryInterface<PartnerRepository>> => {
|
||||
return {
|
||||
create: vitest.fn(),
|
||||
remove: vitest.fn(),
|
||||
|
|
|
@ -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<IPersonRepository> => {
|
||||
export const newPersonRepositoryMock = (): Mocked<RepositoryInterface<PersonRepository>> => {
|
||||
return {
|
||||
getById: vitest.fn(),
|
||||
getAll: vitest.fn(),
|
||||
|
|
|
@ -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<ISearchRepository> => {
|
||||
export const newSearchRepositoryMock = (): Mocked<RepositoryInterface<SearchRepository>> => {
|
||||
return {
|
||||
searchMetadata: vitest.fn(),
|
||||
searchSmart: vitest.fn(),
|
||||
|
|
|
@ -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<ISharedLinkRepository> => {
|
||||
export const newSharedLinkRepositoryMock = (): Mocked<RepositoryInterface<SharedLinkRepository>> => {
|
||||
return {
|
||||
getAll: vitest.fn(),
|
||||
get: vitest.fn(),
|
||||
|
|
|
@ -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<IStackRepository> => {
|
||||
export const newStackRepositoryMock = (): Mocked<RepositoryInterface<StackRepository>> => {
|
||||
return {
|
||||
search: vitest.fn(),
|
||||
create: vitest.fn(),
|
||||
|
|
|
@ -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<IStorageRepository> => {
|
||||
export const newStorageRepositoryMock = (reset = true): Mocked<RepositoryInterface<StorageRepository>> => {
|
||||
if (reset) {
|
||||
StorageCore.reset();
|
||||
}
|
||||
|
|
|
@ -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<ITagRepository> => {
|
||||
export const newTagRepositoryMock = (): Mocked<RepositoryInterface<TagRepository>> => {
|
||||
return {
|
||||
getAll: vitest.fn(),
|
||||
getByValue: vitest.fn(),
|
||||
|
|
|
@ -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<IUserRepository> => {
|
||||
export const newUserRepositoryMock = (): Mocked<RepositoryInterface<UserRepository>> => {
|
||||
return {
|
||||
get: vitest.fn(),
|
||||
getAdmin: vitest.fn(),
|
||||
|
|
|
@ -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<Ac
|
|||
export type ServiceMocks = {
|
||||
access: IAccessRepositoryMock;
|
||||
activity: Mocked<RepositoryInterface<ActivityRepository>>;
|
||||
album: Mocked<IAlbumRepository>;
|
||||
album: Mocked<RepositoryInterface<AlbumRepository>>;
|
||||
albumUser: Mocked<RepositoryInterface<AlbumUserRepository>>;
|
||||
apiKey: Mocked<RepositoryInterface<ApiKeyRepository>>;
|
||||
audit: Mocked<RepositoryInterface<AuditRepository>>;
|
||||
asset: Mocked<IAssetRepository>;
|
||||
asset: Mocked<RepositoryInterface<AssetRepository>>;
|
||||
config: Mocked<RepositoryInterface<ConfigRepository>>;
|
||||
cron: Mocked<RepositoryInterface<CronRepository>>;
|
||||
crypto: Mocked<ICryptoRepository>;
|
||||
crypto: Mocked<RepositoryInterface<CryptoRepository>>;
|
||||
database: Mocked<RepositoryInterface<DatabaseRepository>>;
|
||||
event: Mocked<IEventRepository>;
|
||||
job: Mocked<RepositoryInterface<JobRepository>>;
|
||||
|
@ -134,7 +133,7 @@ export type ServiceMocks = {
|
|||
tag: Mocked<RepositoryInterface<TagRepository>>;
|
||||
telemetry: ITelemetryRepositoryMock;
|
||||
trash: Mocked<RepositoryInterface<TrashRepository>>;
|
||||
user: Mocked<IUserRepository>;
|
||||
user: Mocked<RepositoryInterface<UserRepository>>;
|
||||
versionHistory: Mocked<RepositoryInterface<VersionHistoryRepository>>;
|
||||
view: Mocked<RepositoryInterface<ViewRepository>>;
|
||||
};
|
||||
|
@ -192,39 +191,39 @@ export const newTestService = <T extends BaseService>(
|
|||
accessMock as IAccessRepository as AccessRepository,
|
||||
activityMock as RepositoryInterface<ActivityRepository> as ActivityRepository,
|
||||
auditMock as RepositoryInterface<AuditRepository> as AuditRepository,
|
||||
albumMock,
|
||||
albumMock as RepositoryInterface<AlbumRepository> as AlbumRepository,
|
||||
albumUserMock as RepositoryInterface<AlbumUserRepository> as AlbumUserRepository,
|
||||
assetMock,
|
||||
assetMock as RepositoryInterface<AssetRepository> as AssetRepository,
|
||||
configMock,
|
||||
cronMock as RepositoryInterface<CronRepository> as CronRepository,
|
||||
cryptoMock as RepositoryInterface<CryptoRepository> as CryptoRepository,
|
||||
databaseMock,
|
||||
databaseMock as RepositoryInterface<DatabaseRepository> as DatabaseRepository,
|
||||
eventMock,
|
||||
jobMock,
|
||||
apiKeyMock as RepositoryInterface<ApiKeyRepository> as ApiKeyRepository,
|
||||
libraryMock,
|
||||
libraryMock as RepositoryInterface<LibraryRepository> as LibraryRepository,
|
||||
machineLearningMock,
|
||||
mapMock as RepositoryInterface<MapRepository> as MapRepository,
|
||||
mediaMock as RepositoryInterface<MediaRepository> as MediaRepository,
|
||||
memoryMock as RepositoryInterface<MemoryRepository> as MemoryRepository,
|
||||
metadataMock as RepositoryInterface<MetadataRepository> as MetadataRepository,
|
||||
moveMock,
|
||||
moveMock as RepositoryInterface<MoveRepository> as MoveRepository,
|
||||
notificationMock as RepositoryInterface<NotificationRepository> as NotificationRepository,
|
||||
oauthMock as RepositoryInterface<OAuthRepository> as OAuthRepository,
|
||||
partnerMock,
|
||||
personMock,
|
||||
partnerMock as RepositoryInterface<PartnerRepository> as PartnerRepository,
|
||||
personMock as RepositoryInterface<PersonRepository> as PersonRepository,
|
||||
processMock as RepositoryInterface<ProcessRepository> as ProcessRepository,
|
||||
searchMock,
|
||||
searchMock as RepositoryInterface<SearchRepository> as SearchRepository,
|
||||
serverInfoMock as RepositoryInterface<ServerInfoRepository> as ServerInfoRepository,
|
||||
sessionMock as RepositoryInterface<SessionRepository> as SessionRepository,
|
||||
sharedLinkMock,
|
||||
stackMock,
|
||||
storageMock,
|
||||
sharedLinkMock as RepositoryInterface<SharedLinkRepository> as SharedLinkRepository,
|
||||
stackMock as RepositoryInterface<StackRepository> as StackRepository,
|
||||
storageMock as RepositoryInterface<StorageRepository> as StorageRepository,
|
||||
systemMock as RepositoryInterface<SystemMetadataRepository> as SystemMetadataRepository,
|
||||
tagMock,
|
||||
tagMock as RepositoryInterface<TagRepository> as TagRepository,
|
||||
telemetryMock as unknown as TelemetryRepository,
|
||||
trashMock as RepositoryInterface<TrashRepository> as TrashRepository,
|
||||
userMock,
|
||||
userMock as RepositoryInterface<UserRepository> as UserRepository,
|
||||
versionHistoryMock as RepositoryInterface<VersionHistoryRepository> as VersionHistoryRepository,
|
||||
viewMock as RepositoryInterface<ViewRepository> as ViewRepository,
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue