mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	refactor(server): view repository (#12755)
This commit is contained in:
		
							parent
							
								
									f53e4721cf
								
							
						
					
					
						commit
						1e6ef5c9e4
					
				@ -147,8 +147,6 @@ export type AssetPathEntity = Pick<AssetEntity, 'id' | 'originalPath' | 'isOffli
 | 
			
		||||
export const IAssetRepository = 'IAssetRepository';
 | 
			
		||||
 | 
			
		||||
export interface IAssetRepository {
 | 
			
		||||
  getAssetsByOriginalPath(userId: string, partialPath: string): Promise<AssetEntity[]>;
 | 
			
		||||
  getUniqueOriginalPaths(userId: string): Promise<string[]>;
 | 
			
		||||
  create(asset: AssetCreate): Promise<AssetEntity>;
 | 
			
		||||
  getByIds(
 | 
			
		||||
    ids: string[],
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								server/src/interfaces/view.interface.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								server/src/interfaces/view.interface.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
import { AssetEntity } from 'src/entities/asset.entity';
 | 
			
		||||
 | 
			
		||||
export const IViewRepository = 'IViewRepository';
 | 
			
		||||
 | 
			
		||||
export interface IViewRepository {
 | 
			
		||||
  getAssetsByOriginalPath(userId: string, partialPath: string): Promise<AssetEntity[]>;
 | 
			
		||||
  getUniqueOriginalPaths(userId: string): Promise<string[]>;
 | 
			
		||||
}
 | 
			
		||||
@ -1134,109 +1134,6 @@ WHERE
 | 
			
		||||
  AND "asset"."ownerId" IN ($1)
 | 
			
		||||
  AND "asset"."updatedAt" > $2
 | 
			
		||||
 | 
			
		||||
-- AssetRepository.getAssetsByOriginalPath
 | 
			
		||||
SELECT
 | 
			
		||||
  "asset"."id" AS "asset_id",
 | 
			
		||||
  "asset"."deviceAssetId" AS "asset_deviceAssetId",
 | 
			
		||||
  "asset"."ownerId" AS "asset_ownerId",
 | 
			
		||||
  "asset"."libraryId" AS "asset_libraryId",
 | 
			
		||||
  "asset"."deviceId" AS "asset_deviceId",
 | 
			
		||||
  "asset"."type" AS "asset_type",
 | 
			
		||||
  "asset"."originalPath" AS "asset_originalPath",
 | 
			
		||||
  "asset"."thumbhash" AS "asset_thumbhash",
 | 
			
		||||
  "asset"."encodedVideoPath" AS "asset_encodedVideoPath",
 | 
			
		||||
  "asset"."createdAt" AS "asset_createdAt",
 | 
			
		||||
  "asset"."updatedAt" AS "asset_updatedAt",
 | 
			
		||||
  "asset"."deletedAt" AS "asset_deletedAt",
 | 
			
		||||
  "asset"."fileCreatedAt" AS "asset_fileCreatedAt",
 | 
			
		||||
  "asset"."localDateTime" AS "asset_localDateTime",
 | 
			
		||||
  "asset"."fileModifiedAt" AS "asset_fileModifiedAt",
 | 
			
		||||
  "asset"."isFavorite" AS "asset_isFavorite",
 | 
			
		||||
  "asset"."isArchived" AS "asset_isArchived",
 | 
			
		||||
  "asset"."isExternal" AS "asset_isExternal",
 | 
			
		||||
  "asset"."isOffline" AS "asset_isOffline",
 | 
			
		||||
  "asset"."checksum" AS "asset_checksum",
 | 
			
		||||
  "asset"."duration" AS "asset_duration",
 | 
			
		||||
  "asset"."isVisible" AS "asset_isVisible",
 | 
			
		||||
  "asset"."livePhotoVideoId" AS "asset_livePhotoVideoId",
 | 
			
		||||
  "asset"."originalFileName" AS "asset_originalFileName",
 | 
			
		||||
  "asset"."sidecarPath" AS "asset_sidecarPath",
 | 
			
		||||
  "asset"."stackId" AS "asset_stackId",
 | 
			
		||||
  "asset"."duplicateId" AS "asset_duplicateId",
 | 
			
		||||
  "exifInfo"."assetId" AS "exifInfo_assetId",
 | 
			
		||||
  "exifInfo"."description" AS "exifInfo_description",
 | 
			
		||||
  "exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
 | 
			
		||||
  "exifInfo"."exifImageHeight" AS "exifInfo_exifImageHeight",
 | 
			
		||||
  "exifInfo"."fileSizeInByte" AS "exifInfo_fileSizeInByte",
 | 
			
		||||
  "exifInfo"."orientation" AS "exifInfo_orientation",
 | 
			
		||||
  "exifInfo"."dateTimeOriginal" AS "exifInfo_dateTimeOriginal",
 | 
			
		||||
  "exifInfo"."modifyDate" AS "exifInfo_modifyDate",
 | 
			
		||||
  "exifInfo"."timeZone" AS "exifInfo_timeZone",
 | 
			
		||||
  "exifInfo"."latitude" AS "exifInfo_latitude",
 | 
			
		||||
  "exifInfo"."longitude" AS "exifInfo_longitude",
 | 
			
		||||
  "exifInfo"."projectionType" AS "exifInfo_projectionType",
 | 
			
		||||
  "exifInfo"."city" AS "exifInfo_city",
 | 
			
		||||
  "exifInfo"."livePhotoCID" AS "exifInfo_livePhotoCID",
 | 
			
		||||
  "exifInfo"."autoStackId" AS "exifInfo_autoStackId",
 | 
			
		||||
  "exifInfo"."state" AS "exifInfo_state",
 | 
			
		||||
  "exifInfo"."country" AS "exifInfo_country",
 | 
			
		||||
  "exifInfo"."make" AS "exifInfo_make",
 | 
			
		||||
  "exifInfo"."model" AS "exifInfo_model",
 | 
			
		||||
  "exifInfo"."lensModel" AS "exifInfo_lensModel",
 | 
			
		||||
  "exifInfo"."fNumber" AS "exifInfo_fNumber",
 | 
			
		||||
  "exifInfo"."focalLength" AS "exifInfo_focalLength",
 | 
			
		||||
  "exifInfo"."iso" AS "exifInfo_iso",
 | 
			
		||||
  "exifInfo"."exposureTime" AS "exifInfo_exposureTime",
 | 
			
		||||
  "exifInfo"."profileDescription" AS "exifInfo_profileDescription",
 | 
			
		||||
  "exifInfo"."colorspace" AS "exifInfo_colorspace",
 | 
			
		||||
  "exifInfo"."bitsPerSample" AS "exifInfo_bitsPerSample",
 | 
			
		||||
  "exifInfo"."rating" AS "exifInfo_rating",
 | 
			
		||||
  "exifInfo"."fps" AS "exifInfo_fps",
 | 
			
		||||
  "stack"."id" AS "stack_id",
 | 
			
		||||
  "stack"."ownerId" AS "stack_ownerId",
 | 
			
		||||
  "stack"."primaryAssetId" AS "stack_primaryAssetId",
 | 
			
		||||
  "stackedAssets"."id" AS "stackedAssets_id",
 | 
			
		||||
  "stackedAssets"."deviceAssetId" AS "stackedAssets_deviceAssetId",
 | 
			
		||||
  "stackedAssets"."ownerId" AS "stackedAssets_ownerId",
 | 
			
		||||
  "stackedAssets"."libraryId" AS "stackedAssets_libraryId",
 | 
			
		||||
  "stackedAssets"."deviceId" AS "stackedAssets_deviceId",
 | 
			
		||||
  "stackedAssets"."type" AS "stackedAssets_type",
 | 
			
		||||
  "stackedAssets"."originalPath" AS "stackedAssets_originalPath",
 | 
			
		||||
  "stackedAssets"."thumbhash" AS "stackedAssets_thumbhash",
 | 
			
		||||
  "stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath",
 | 
			
		||||
  "stackedAssets"."createdAt" AS "stackedAssets_createdAt",
 | 
			
		||||
  "stackedAssets"."updatedAt" AS "stackedAssets_updatedAt",
 | 
			
		||||
  "stackedAssets"."deletedAt" AS "stackedAssets_deletedAt",
 | 
			
		||||
  "stackedAssets"."fileCreatedAt" AS "stackedAssets_fileCreatedAt",
 | 
			
		||||
  "stackedAssets"."localDateTime" AS "stackedAssets_localDateTime",
 | 
			
		||||
  "stackedAssets"."fileModifiedAt" AS "stackedAssets_fileModifiedAt",
 | 
			
		||||
  "stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
 | 
			
		||||
  "stackedAssets"."isArchived" AS "stackedAssets_isArchived",
 | 
			
		||||
  "stackedAssets"."isExternal" AS "stackedAssets_isExternal",
 | 
			
		||||
  "stackedAssets"."isOffline" AS "stackedAssets_isOffline",
 | 
			
		||||
  "stackedAssets"."checksum" AS "stackedAssets_checksum",
 | 
			
		||||
  "stackedAssets"."duration" AS "stackedAssets_duration",
 | 
			
		||||
  "stackedAssets"."isVisible" AS "stackedAssets_isVisible",
 | 
			
		||||
  "stackedAssets"."livePhotoVideoId" AS "stackedAssets_livePhotoVideoId",
 | 
			
		||||
  "stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
 | 
			
		||||
  "stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
 | 
			
		||||
  "stackedAssets"."stackId" AS "stackedAssets_stackId",
 | 
			
		||||
  "stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
 | 
			
		||||
FROM
 | 
			
		||||
  "assets" "asset"
 | 
			
		||||
  LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
 | 
			
		||||
  LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId"
 | 
			
		||||
  LEFT JOIN "assets" "stackedAssets" ON "stackedAssets"."stackId" = "stack"."id"
 | 
			
		||||
  AND ("stackedAssets"."deletedAt" IS NULL)
 | 
			
		||||
WHERE
 | 
			
		||||
  "asset"."ownerId" = $1
 | 
			
		||||
  AND (
 | 
			
		||||
    "asset"."originalPath" LIKE $2
 | 
			
		||||
    AND "asset"."originalPath" NOT LIKE $3
 | 
			
		||||
  )
 | 
			
		||||
ORDER BY
 | 
			
		||||
  regexp_replace("asset"."originalPath", '.*/(.+)', '\1') ASC
 | 
			
		||||
 | 
			
		||||
-- AssetRepository.upsertFile
 | 
			
		||||
INSERT INTO
 | 
			
		||||
  "asset_files" (
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										78
									
								
								server/src/queries/view.repository.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								server/src/queries/view.repository.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,78 @@
 | 
			
		||||
-- NOTE: This file is auto generated by ./sql-generator
 | 
			
		||||
 | 
			
		||||
-- ViewRepository.getAssetsByOriginalPath
 | 
			
		||||
SELECT
 | 
			
		||||
  "asset"."id" AS "asset_id",
 | 
			
		||||
  "asset"."deviceAssetId" AS "asset_deviceAssetId",
 | 
			
		||||
  "asset"."ownerId" AS "asset_ownerId",
 | 
			
		||||
  "asset"."libraryId" AS "asset_libraryId",
 | 
			
		||||
  "asset"."deviceId" AS "asset_deviceId",
 | 
			
		||||
  "asset"."type" AS "asset_type",
 | 
			
		||||
  "asset"."originalPath" AS "asset_originalPath",
 | 
			
		||||
  "asset"."thumbhash" AS "asset_thumbhash",
 | 
			
		||||
  "asset"."encodedVideoPath" AS "asset_encodedVideoPath",
 | 
			
		||||
  "asset"."createdAt" AS "asset_createdAt",
 | 
			
		||||
  "asset"."updatedAt" AS "asset_updatedAt",
 | 
			
		||||
  "asset"."deletedAt" AS "asset_deletedAt",
 | 
			
		||||
  "asset"."fileCreatedAt" AS "asset_fileCreatedAt",
 | 
			
		||||
  "asset"."localDateTime" AS "asset_localDateTime",
 | 
			
		||||
  "asset"."fileModifiedAt" AS "asset_fileModifiedAt",
 | 
			
		||||
  "asset"."isFavorite" AS "asset_isFavorite",
 | 
			
		||||
  "asset"."isArchived" AS "asset_isArchived",
 | 
			
		||||
  "asset"."isExternal" AS "asset_isExternal",
 | 
			
		||||
  "asset"."isOffline" AS "asset_isOffline",
 | 
			
		||||
  "asset"."checksum" AS "asset_checksum",
 | 
			
		||||
  "asset"."duration" AS "asset_duration",
 | 
			
		||||
  "asset"."isVisible" AS "asset_isVisible",
 | 
			
		||||
  "asset"."livePhotoVideoId" AS "asset_livePhotoVideoId",
 | 
			
		||||
  "asset"."originalFileName" AS "asset_originalFileName",
 | 
			
		||||
  "asset"."sidecarPath" AS "asset_sidecarPath",
 | 
			
		||||
  "asset"."stackId" AS "asset_stackId",
 | 
			
		||||
  "asset"."duplicateId" AS "asset_duplicateId",
 | 
			
		||||
  "exifInfo"."assetId" AS "exifInfo_assetId",
 | 
			
		||||
  "exifInfo"."description" AS "exifInfo_description",
 | 
			
		||||
  "exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
 | 
			
		||||
  "exifInfo"."exifImageHeight" AS "exifInfo_exifImageHeight",
 | 
			
		||||
  "exifInfo"."fileSizeInByte" AS "exifInfo_fileSizeInByte",
 | 
			
		||||
  "exifInfo"."orientation" AS "exifInfo_orientation",
 | 
			
		||||
  "exifInfo"."dateTimeOriginal" AS "exifInfo_dateTimeOriginal",
 | 
			
		||||
  "exifInfo"."modifyDate" AS "exifInfo_modifyDate",
 | 
			
		||||
  "exifInfo"."timeZone" AS "exifInfo_timeZone",
 | 
			
		||||
  "exifInfo"."latitude" AS "exifInfo_latitude",
 | 
			
		||||
  "exifInfo"."longitude" AS "exifInfo_longitude",
 | 
			
		||||
  "exifInfo"."projectionType" AS "exifInfo_projectionType",
 | 
			
		||||
  "exifInfo"."city" AS "exifInfo_city",
 | 
			
		||||
  "exifInfo"."livePhotoCID" AS "exifInfo_livePhotoCID",
 | 
			
		||||
  "exifInfo"."autoStackId" AS "exifInfo_autoStackId",
 | 
			
		||||
  "exifInfo"."state" AS "exifInfo_state",
 | 
			
		||||
  "exifInfo"."country" AS "exifInfo_country",
 | 
			
		||||
  "exifInfo"."make" AS "exifInfo_make",
 | 
			
		||||
  "exifInfo"."model" AS "exifInfo_model",
 | 
			
		||||
  "exifInfo"."lensModel" AS "exifInfo_lensModel",
 | 
			
		||||
  "exifInfo"."fNumber" AS "exifInfo_fNumber",
 | 
			
		||||
  "exifInfo"."focalLength" AS "exifInfo_focalLength",
 | 
			
		||||
  "exifInfo"."iso" AS "exifInfo_iso",
 | 
			
		||||
  "exifInfo"."exposureTime" AS "exifInfo_exposureTime",
 | 
			
		||||
  "exifInfo"."profileDescription" AS "exifInfo_profileDescription",
 | 
			
		||||
  "exifInfo"."colorspace" AS "exifInfo_colorspace",
 | 
			
		||||
  "exifInfo"."bitsPerSample" AS "exifInfo_bitsPerSample",
 | 
			
		||||
  "exifInfo"."rating" AS "exifInfo_rating",
 | 
			
		||||
  "exifInfo"."fps" AS "exifInfo_fps"
 | 
			
		||||
FROM
 | 
			
		||||
  "assets" "asset"
 | 
			
		||||
  LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
 | 
			
		||||
WHERE
 | 
			
		||||
  (
 | 
			
		||||
    (
 | 
			
		||||
      "asset"."isVisible" = $1
 | 
			
		||||
      AND "asset"."isArchived" = $2
 | 
			
		||||
      AND "asset"."ownerId" = $3
 | 
			
		||||
    )
 | 
			
		||||
    AND (
 | 
			
		||||
      "asset"."originalPath" LIKE $4
 | 
			
		||||
      AND "asset"."originalPath" NOT LIKE $5
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  AND ("asset"."deletedAt" IS NULL)
 | 
			
		||||
ORDER BY
 | 
			
		||||
  regexp_replace("asset"."originalPath", '.*/(.+)', '\1') ASC
 | 
			
		||||
@ -836,50 +836,6 @@ export class AssetRepository implements IAssetRepository {
 | 
			
		||||
    return builder.getMany();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getUniqueOriginalPaths(userId: string): Promise<string[]> {
 | 
			
		||||
    const builder = this.getBuilder({
 | 
			
		||||
      userIds: [userId],
 | 
			
		||||
      exifInfo: false,
 | 
			
		||||
      withStacked: false,
 | 
			
		||||
      isArchived: false,
 | 
			
		||||
      isTrashed: false,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const results = await builder
 | 
			
		||||
      .select("DISTINCT substring(asset.originalPath FROM '^(.*/)[^/]*$')", 'directoryPath')
 | 
			
		||||
      .getRawMany();
 | 
			
		||||
 | 
			
		||||
    return results.map((row: { directoryPath: string }) => row.directoryPath.replaceAll(/^\/|\/$/g, ''));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
 | 
			
		||||
  async getAssetsByOriginalPath(userId: string, partialPath: string): Promise<AssetEntity[]> {
 | 
			
		||||
    const normalizedPath = partialPath.replaceAll(/^\/|\/$/g, '');
 | 
			
		||||
 | 
			
		||||
    const builder = this.getBuilder({
 | 
			
		||||
      userIds: [userId],
 | 
			
		||||
      exifInfo: true,
 | 
			
		||||
      withStacked: false,
 | 
			
		||||
      isArchived: false,
 | 
			
		||||
      isTrashed: false,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const assets = await builder
 | 
			
		||||
      .where('asset.ownerId = :userId', { userId })
 | 
			
		||||
      .andWhere(
 | 
			
		||||
        new Brackets((qb) => {
 | 
			
		||||
          qb.where('asset.originalPath LIKE :likePath', { likePath: `%${normalizedPath}/%` }).andWhere(
 | 
			
		||||
            'asset.originalPath NOT LIKE :notLikePath',
 | 
			
		||||
            { notLikePath: `%${normalizedPath}/%/%` },
 | 
			
		||||
          );
 | 
			
		||||
        }),
 | 
			
		||||
      )
 | 
			
		||||
      .orderBy(String.raw`regexp_replace(asset.originalPath, '.*/(.+)', '\1')`, 'ASC')
 | 
			
		||||
      .getMany();
 | 
			
		||||
 | 
			
		||||
    return assets;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @GenerateSql({ params: [{ assetId: DummyValue.UUID, type: AssetFileType.PREVIEW, path: '/path/to/file' }] })
 | 
			
		||||
  async upsertFile({ assetId, type, path }: { assetId: string; type: AssetFileType; path: string }): Promise<void> {
 | 
			
		||||
    await this.fileRepository.upsert({ assetId, type, path }, { conflictPaths: ['assetId', 'type'] });
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,7 @@ import { IStorageRepository } from 'src/interfaces/storage.interface';
 | 
			
		||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
 | 
			
		||||
import { ITagRepository } from 'src/interfaces/tag.interface';
 | 
			
		||||
import { IUserRepository } from 'src/interfaces/user.interface';
 | 
			
		||||
import { IViewRepository } from 'src/interfaces/view.interface';
 | 
			
		||||
import { AccessRepository } from 'src/repositories/access.repository';
 | 
			
		||||
import { ActivityRepository } from 'src/repositories/activity.repository';
 | 
			
		||||
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
 | 
			
		||||
@ -62,6 +63,7 @@ import { StorageRepository } from 'src/repositories/storage.repository';
 | 
			
		||||
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
 | 
			
		||||
import { TagRepository } from 'src/repositories/tag.repository';
 | 
			
		||||
import { UserRepository } from 'src/repositories/user.repository';
 | 
			
		||||
import { ViewRepository } from 'src/repositories/view-repository';
 | 
			
		||||
 | 
			
		||||
export const repositories = [
 | 
			
		||||
  { provide: IAccessRepository, useClass: AccessRepository },
 | 
			
		||||
@ -96,4 +98,5 @@ export const repositories = [
 | 
			
		||||
  { provide: ISystemMetadataRepository, useClass: SystemMetadataRepository },
 | 
			
		||||
  { provide: ITagRepository, useClass: TagRepository },
 | 
			
		||||
  { provide: IUserRepository, useClass: UserRepository },
 | 
			
		||||
  { provide: IViewRepository, useClass: ViewRepository },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										48
									
								
								server/src/repositories/view-repository.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								server/src/repositories/view-repository.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
import { InjectRepository } from '@nestjs/typeorm';
 | 
			
		||||
import { DummyValue, GenerateSql } from 'src/decorators';
 | 
			
		||||
import { AssetEntity } from 'src/entities/asset.entity';
 | 
			
		||||
import { IViewRepository } from 'src/interfaces/view.interface';
 | 
			
		||||
import { Brackets, Repository } from 'typeorm';
 | 
			
		||||
 | 
			
		||||
export class ViewRepository implements IViewRepository {
 | 
			
		||||
  constructor(@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>) {}
 | 
			
		||||
 | 
			
		||||
  async getUniqueOriginalPaths(userId: string): Promise<string[]> {
 | 
			
		||||
    const results = await this.assetRepository
 | 
			
		||||
      .createQueryBuilder('asset')
 | 
			
		||||
      .where({
 | 
			
		||||
        isVisible: true,
 | 
			
		||||
        isArchived: false,
 | 
			
		||||
        ownerId: userId,
 | 
			
		||||
      })
 | 
			
		||||
      .select("DISTINCT substring(asset.originalPath FROM '^(.*/)[^/]*$')", 'directoryPath')
 | 
			
		||||
      .getRawMany();
 | 
			
		||||
 | 
			
		||||
    return results.map((row: { directoryPath: string }) => row.directoryPath.replaceAll(/^\/|\/$/g, ''));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
 | 
			
		||||
  async getAssetsByOriginalPath(userId: string, partialPath: string): Promise<AssetEntity[]> {
 | 
			
		||||
    const normalizedPath = partialPath.replaceAll(/^\/|\/$/g, '');
 | 
			
		||||
    const assets = await this.assetRepository
 | 
			
		||||
      .createQueryBuilder('asset')
 | 
			
		||||
      .where({
 | 
			
		||||
        isVisible: true,
 | 
			
		||||
        isArchived: false,
 | 
			
		||||
        ownerId: userId,
 | 
			
		||||
      })
 | 
			
		||||
      .leftJoinAndSelect('asset.exifInfo', 'exifInfo')
 | 
			
		||||
      .andWhere(
 | 
			
		||||
        new Brackets((qb) => {
 | 
			
		||||
          qb.where('asset.originalPath LIKE :likePath', { likePath: `%${normalizedPath}/%` }).andWhere(
 | 
			
		||||
            'asset.originalPath NOT LIKE :notLikePath',
 | 
			
		||||
            { notLikePath: `%${normalizedPath}/%/%` },
 | 
			
		||||
          );
 | 
			
		||||
        }),
 | 
			
		||||
      )
 | 
			
		||||
      .orderBy(String.raw`regexp_replace(asset.originalPath, '.*/(.+)', '\1')`, 'ASC')
 | 
			
		||||
      .getMany();
 | 
			
		||||
 | 
			
		||||
    return assets;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,21 +1,20 @@
 | 
			
		||||
import { mapAsset } from 'src/dtos/asset-response.dto';
 | 
			
		||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
			
		||||
 | 
			
		||||
import { IViewRepository } from 'src/interfaces/view.interface';
 | 
			
		||||
import { ViewService } from 'src/services/view.service';
 | 
			
		||||
import { assetStub } from 'test/fixtures/asset.stub';
 | 
			
		||||
import { authStub } from 'test/fixtures/auth.stub';
 | 
			
		||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
 | 
			
		||||
import { newViewRepositoryMock } from 'test/repositories/view.repository.mock';
 | 
			
		||||
 | 
			
		||||
import { Mocked } from 'vitest';
 | 
			
		||||
 | 
			
		||||
describe(ViewService.name, () => {
 | 
			
		||||
  let sut: ViewService;
 | 
			
		||||
  let assetMock: Mocked<IAssetRepository>;
 | 
			
		||||
  let viewMock: Mocked<IViewRepository>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    assetMock = newAssetRepositoryMock();
 | 
			
		||||
    viewMock = newViewRepositoryMock();
 | 
			
		||||
 | 
			
		||||
    sut = new ViewService(assetMock);
 | 
			
		||||
    sut = new ViewService(viewMock);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should work', () => {
 | 
			
		||||
@ -25,12 +24,12 @@ describe(ViewService.name, () => {
 | 
			
		||||
  describe('getUniqueOriginalPaths', () => {
 | 
			
		||||
    it('should return unique original paths', async () => {
 | 
			
		||||
      const mockPaths = ['path1', 'path2', 'path3'];
 | 
			
		||||
      assetMock.getUniqueOriginalPaths.mockResolvedValue(mockPaths);
 | 
			
		||||
      viewMock.getUniqueOriginalPaths.mockResolvedValue(mockPaths);
 | 
			
		||||
 | 
			
		||||
      const result = await sut.getUniqueOriginalPaths(authStub.admin);
 | 
			
		||||
 | 
			
		||||
      expect(result).toEqual(mockPaths);
 | 
			
		||||
      expect(assetMock.getUniqueOriginalPaths).toHaveBeenCalledWith(authStub.admin.user.id);
 | 
			
		||||
      expect(viewMock.getUniqueOriginalPaths).toHaveBeenCalledWith(authStub.admin.user.id);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@ -45,11 +44,11 @@ describe(ViewService.name, () => {
 | 
			
		||||
 | 
			
		||||
      const mockAssetReponseDto = mockAssets.map((a) => mapAsset(a, { auth: authStub.admin }));
 | 
			
		||||
 | 
			
		||||
      assetMock.getAssetsByOriginalPath.mockResolvedValue(mockAssets);
 | 
			
		||||
      viewMock.getAssetsByOriginalPath.mockResolvedValue(mockAssets);
 | 
			
		||||
 | 
			
		||||
      const result = await sut.getAssetsByOriginalPath(authStub.admin, path);
 | 
			
		||||
      expect(result).toEqual(mockAssetReponseDto);
 | 
			
		||||
      await expect(assetMock.getAssetsByOriginalPath(authStub.admin.user.id, path)).resolves.toEqual(mockAssets);
 | 
			
		||||
      await expect(viewMock.getAssetsByOriginalPath(authStub.admin.user.id, path)).resolves.toEqual(mockAssets);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -1,18 +1,17 @@
 | 
			
		||||
import { Inject } from '@nestjs/common';
 | 
			
		||||
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
 | 
			
		||||
import { AuthDto } from 'src/dtos/auth.dto';
 | 
			
		||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
 | 
			
		||||
import { IViewRepository } from 'src/interfaces/view.interface';
 | 
			
		||||
 | 
			
		||||
export class ViewService {
 | 
			
		||||
  constructor(@Inject(IAssetRepository) private assetRepository: IAssetRepository) {}
 | 
			
		||||
  constructor(@Inject(IViewRepository) private viewRepository: IViewRepository) {}
 | 
			
		||||
 | 
			
		||||
  getUniqueOriginalPaths(auth: AuthDto): Promise<string[]> {
 | 
			
		||||
    return this.assetRepository.getUniqueOriginalPaths(auth.user.id);
 | 
			
		||||
    return this.viewRepository.getUniqueOriginalPaths(auth.user.id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getAssetsByOriginalPath(auth: AuthDto, path: string): Promise<AssetResponseDto[]> {
 | 
			
		||||
    const assets = await this.assetRepository.getAssetsByOriginalPath(auth.user.id, path);
 | 
			
		||||
 | 
			
		||||
    const assets = await this.viewRepository.getAssetsByOriginalPath(auth.user.id, path);
 | 
			
		||||
    return assets.map((asset) => mapAsset(asset, { auth }));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -43,7 +43,5 @@ export const newAssetRepositoryMock = (): Mocked<IAssetRepository> => {
 | 
			
		||||
    getChangedDeltaSync: vitest.fn(),
 | 
			
		||||
    getDuplicates: vitest.fn(),
 | 
			
		||||
    upsertFile: vitest.fn(),
 | 
			
		||||
    getAssetsByOriginalPath: vitest.fn(),
 | 
			
		||||
    getUniqueOriginalPaths: vitest.fn(),
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								server/test/repositories/view.repository.mock.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								server/test/repositories/view.repository.mock.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
import { IViewRepository } from 'src/interfaces/view.interface';
 | 
			
		||||
import { Mocked, vitest } from 'vitest';
 | 
			
		||||
 | 
			
		||||
export const newViewRepositoryMock = (): Mocked<IViewRepository> => {
 | 
			
		||||
  return {
 | 
			
		||||
    getAssetsByOriginalPath: vitest.fn(),
 | 
			
		||||
    getUniqueOriginalPaths: vitest.fn(),
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user