mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	refactor: repositories (#16036)
This commit is contained in:
		
							parent
							
								
									d2575d8f00
								
							
						
					
					
						commit
						9d85272c2b
					
				@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user