mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 22:51:59 +00:00
refactor(server): move files to separate table (#11861)
This commit is contained in:
parent
af3a793fe8
commit
7af6733665
32 changed files with 403 additions and 210 deletions
|
@ -6,6 +6,7 @@ import { SystemConfigCore } from 'src/cores/system-config.core';
|
|||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { AssetPathType, PathType, PersonPathType } from 'src/entities/move.entity';
|
||||
import { PersonEntity } from 'src/entities/person.entity';
|
||||
import { AssetFileType } from 'src/enum';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
|
@ -13,6 +14,7 @@ import { IMoveRepository } from 'src/interfaces/move.interface';
|
|||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
|
||||
export enum StorageFolder {
|
||||
ENCODED_VIDEO = 'encoded-video',
|
||||
|
@ -130,12 +132,14 @@ export class StorageCore {
|
|||
}
|
||||
|
||||
async moveAssetImage(asset: AssetEntity, pathType: GeneratedImageType, format: ImageFormat) {
|
||||
const { id: entityId, previewPath, thumbnailPath } = asset;
|
||||
const { id: entityId, files } = asset;
|
||||
const { thumbnailFile, previewFile } = getAssetFiles(files);
|
||||
const oldFile = pathType === AssetPathType.PREVIEW ? previewFile : thumbnailFile;
|
||||
return this.moveFile({
|
||||
entityId,
|
||||
pathType,
|
||||
oldPath: pathType === AssetPathType.PREVIEW ? previewPath : thumbnailPath,
|
||||
newPath: StorageCore.getImagePath(asset, AssetPathType.THUMBNAIL, format),
|
||||
oldPath: oldFile?.path || null,
|
||||
newPath: StorageCore.getImagePath(asset, pathType, format),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -285,10 +289,10 @@ export class StorageCore {
|
|||
return this.assetRepository.update({ id, originalPath: newPath });
|
||||
}
|
||||
case AssetPathType.PREVIEW: {
|
||||
return this.assetRepository.update({ id, previewPath: newPath });
|
||||
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.PREVIEW, path: newPath });
|
||||
}
|
||||
case AssetPathType.THUMBNAIL: {
|
||||
return this.assetRepository.update({ id, thumbnailPath: newPath });
|
||||
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.THUMBNAIL, path: newPath });
|
||||
}
|
||||
case AssetPathType.ENCODED_VIDEO: {
|
||||
return this.assetRepository.update({ id, encodedVideoPath: newPath });
|
||||
|
|
|
@ -14,6 +14,7 @@ import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
|||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { SmartInfoEntity } from 'src/entities/smart-info.entity';
|
||||
import { AssetType } from 'src/enum';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { mimeTypes } from 'src/utils/mime-types';
|
||||
|
||||
export class SanitizedAssetResponseDto {
|
||||
|
@ -111,7 +112,7 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
|
|||
originalMimeType: mimeTypes.lookup(entity.originalFileName),
|
||||
thumbhash: entity.thumbhash?.toString('base64') ?? null,
|
||||
localDateTime: entity.localDateTime,
|
||||
resized: !!entity.previewPath,
|
||||
resized: !!getAssetFiles(entity.files).previewFile,
|
||||
duration: entity.duration ?? '0:00:00.00000',
|
||||
livePhotoVideoId: entity.livePhotoVideoId,
|
||||
hasMetadata: false,
|
||||
|
@ -130,7 +131,7 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
|
|||
originalPath: entity.originalPath,
|
||||
originalFileName: entity.originalFileName,
|
||||
originalMimeType: mimeTypes.lookup(entity.originalFileName),
|
||||
resized: !!entity.previewPath,
|
||||
resized: !!getAssetFiles(entity.files).previewFile,
|
||||
thumbhash: entity.thumbhash?.toString('base64') ?? null,
|
||||
fileCreatedAt: entity.fileCreatedAt,
|
||||
fileModifiedAt: entity.fileModifiedAt,
|
||||
|
|
38
server/src/entities/asset-files.entity.ts
Normal file
38
server/src/entities/asset-files.entity.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { AssetFileType } from 'src/enum';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
@Unique('UQ_assetId_type', ['assetId', 'type'])
|
||||
@Entity('asset_files')
|
||||
export class AssetFileEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Index('IDX_asset_files_assetId')
|
||||
@Column()
|
||||
assetId!: string;
|
||||
|
||||
@ManyToOne(() => AssetEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
asset?: AssetEntity;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: Date;
|
||||
|
||||
@Column()
|
||||
type!: AssetFileType;
|
||||
|
||||
@Column()
|
||||
path!: string;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { AlbumEntity } from 'src/entities/album.entity';
|
||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
|
||||
import { ExifEntity } from 'src/entities/exif.entity';
|
||||
import { LibraryEntity } from 'src/entities/library.entity';
|
||||
|
@ -72,11 +73,8 @@ export class AssetEntity {
|
|||
@Column()
|
||||
originalPath!: string;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
previewPath!: string | null;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true, default: '' })
|
||||
thumbnailPath!: string | null;
|
||||
@OneToMany(() => AssetFileEntity, (assetFile) => assetFile.asset)
|
||||
files!: AssetFileEntity[];
|
||||
|
||||
@Column({ type: 'bytea', nullable: true })
|
||||
thumbhash!: Buffer | null;
|
||||
|
|
|
@ -3,6 +3,7 @@ import { AlbumUserEntity } from 'src/entities/album-user.entity';
|
|||
import { AlbumEntity } from 'src/entities/album.entity';
|
||||
import { APIKeyEntity } from 'src/entities/api-key.entity';
|
||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { AuditEntity } from 'src/entities/audit.entity';
|
||||
|
@ -32,6 +33,7 @@ export const entities = [
|
|||
APIKeyEntity,
|
||||
AssetEntity,
|
||||
AssetFaceEntity,
|
||||
AssetFileEntity,
|
||||
AssetJobStatusEntity,
|
||||
AuditEntity,
|
||||
ExifEntity,
|
||||
|
|
|
@ -5,6 +5,11 @@ export enum AssetType {
|
|||
OTHER = 'OTHER',
|
||||
}
|
||||
|
||||
export enum AssetFileType {
|
||||
PREVIEW = 'preview',
|
||||
THUMBNAIL = 'thumbnail',
|
||||
}
|
||||
|
||||
export enum AlbumUserRole {
|
||||
EDITOR = 'editor',
|
||||
VIEWER = 'viewer',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { ExifEntity } from 'src/entities/exif.entity';
|
||||
import { AssetOrder, AssetType } from 'src/enum';
|
||||
import { AssetFileType, AssetOrder, AssetType } from 'src/enum';
|
||||
import { AssetSearchOptions, SearchExploreItem } from 'src/interfaces/search.interface';
|
||||
import { Paginated, PaginationOptions } from 'src/utils/pagination';
|
||||
import { FindOptionsOrder, FindOptionsRelations, FindOptionsSelect } from 'typeorm';
|
||||
|
@ -191,4 +191,5 @@ export interface IAssetRepository {
|
|||
getDuplicates(options: AssetBuilderOptions): Promise<AssetEntity[]>;
|
||||
getAllForUserFullSync(options: AssetFullSyncOptions): Promise<AssetEntity[]>;
|
||||
getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise<AssetEntity[]>;
|
||||
upsertFile(options: { assetId: string; type: AssetFileType; path: string }): Promise<void>;
|
||||
}
|
||||
|
|
34
server/src/migrations/1724101822106-AddAssetFilesTable.ts
Normal file
34
server/src/migrations/1724101822106-AddAssetFilesTable.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddAssetFilesTable1724101822106 implements MigrationInterface {
|
||||
name = 'AddAssetFilesTable1724101822106'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "asset_files" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "assetId" uuid NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "type" character varying NOT NULL, "path" character varying NOT NULL, CONSTRAINT "UQ_assetId_type" UNIQUE ("assetId", "type"), CONSTRAINT "PK_c41dc3e9ef5e1c57ca5a08a0004" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_asset_files_assetId" ON "asset_files" ("assetId") `);
|
||||
await queryRunner.query(`ALTER TABLE "asset_files" ADD CONSTRAINT "FK_e3e103a5f1d8bc8402999286040" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
|
||||
// preview path migration
|
||||
await queryRunner.query(`INSERT INTO "asset_files" ("assetId", "type", "path") SELECT "id", 'preview', "previewPath" FROM "assets" WHERE "previewPath" IS NOT NULL AND "previewPath" != ''`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "previewPath"`);
|
||||
|
||||
// thumbnail path migration
|
||||
await queryRunner.query(`INSERT INTO "asset_files" ("assetId", "type", "path") SELECT "id", 'thumbnail', "thumbnailPath" FROM "assets" WHERE "thumbnailPath" IS NOT NULL AND "thumbnailPath" != ''`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "thumbnailPath"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// undo preview path migration
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD "previewPath" character varying`);
|
||||
await queryRunner.query(`UPDATE "assets" SET "previewPath" = "asset_files".path FROM "asset_files" WHERE "assets".id = "asset_files".assetId AND "asset_files".type = 'preview'`);
|
||||
|
||||
// undo thumbnail path migration
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD "thumbnailPath" character varying DEFAULT ''`);
|
||||
await queryRunner.query(`UPDATE "assets" SET "thumbnailPath" = "asset_files".path FROM "asset_files" WHERE "assets".id = "asset_files".assetId AND "asset_files".type = 'thumbnail'`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "asset_files" DROP CONSTRAINT "FK_e3e103a5f1d8bc8402999286040"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_asset_files_assetId"`);
|
||||
await queryRunner.query(`DROP TABLE "asset_files"`);
|
||||
}
|
||||
|
||||
}
|
|
@ -9,8 +9,6 @@ SELECT
|
|||
"entity"."deviceId" AS "entity_deviceId",
|
||||
"entity"."type" AS "entity_type",
|
||||
"entity"."originalPath" AS "entity_originalPath",
|
||||
"entity"."previewPath" AS "entity_previewPath",
|
||||
"entity"."thumbnailPath" AS "entity_thumbnailPath",
|
||||
"entity"."thumbhash" AS "entity_thumbhash",
|
||||
"entity"."encodedVideoPath" AS "entity_encodedVideoPath",
|
||||
"entity"."createdAt" AS "entity_createdAt",
|
||||
|
@ -59,16 +57,22 @@ SELECT
|
|||
"exifInfo"."colorspace" AS "exifInfo_colorspace",
|
||||
"exifInfo"."bitsPerSample" AS "exifInfo_bitsPerSample",
|
||||
"exifInfo"."rating" AS "exifInfo_rating",
|
||||
"exifInfo"."fps" AS "exifInfo_fps"
|
||||
"exifInfo"."fps" AS "exifInfo_fps",
|
||||
"files"."id" AS "files_id",
|
||||
"files"."assetId" AS "files_assetId",
|
||||
"files"."createdAt" AS "files_createdAt",
|
||||
"files"."updatedAt" AS "files_updatedAt",
|
||||
"files"."type" AS "files_type",
|
||||
"files"."path" AS "files_path"
|
||||
FROM
|
||||
"assets" "entity"
|
||||
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "entity"."id"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "entity"."id"
|
||||
WHERE
|
||||
(
|
||||
"entity"."ownerId" IN ($1)
|
||||
AND "entity"."isVisible" = true
|
||||
AND "entity"."isArchived" = false
|
||||
AND "entity"."previewPath" IS NOT NULL
|
||||
AND EXTRACT(
|
||||
DAY
|
||||
FROM
|
||||
|
@ -93,8 +97,6 @@ SELECT
|
|||
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||
"AssetEntity"."type" AS "AssetEntity_type",
|
||||
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
|
||||
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
|
||||
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||
|
@ -129,8 +131,6 @@ SELECT
|
|||
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||
"AssetEntity"."type" AS "AssetEntity_type",
|
||||
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
|
||||
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
|
||||
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||
|
@ -216,8 +216,6 @@ SELECT
|
|||
"bd93d5747511a4dad4923546c51365bf1a803774"."deviceId" AS "bd93d5747511a4dad4923546c51365bf1a803774_deviceId",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."type" AS "bd93d5747511a4dad4923546c51365bf1a803774_type",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."originalPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_originalPath",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."previewPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_previewPath",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."thumbnailPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_thumbnailPath",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."thumbhash" AS "bd93d5747511a4dad4923546c51365bf1a803774_thumbhash",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."encodedVideoPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_encodedVideoPath",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."createdAt" AS "bd93d5747511a4dad4923546c51365bf1a803774_createdAt",
|
||||
|
@ -237,7 +235,13 @@ SELECT
|
|||
"bd93d5747511a4dad4923546c51365bf1a803774"."originalFileName" AS "bd93d5747511a4dad4923546c51365bf1a803774_originalFileName",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."sidecarPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_sidecarPath",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."stackId" AS "bd93d5747511a4dad4923546c51365bf1a803774_stackId",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."duplicateId" AS "bd93d5747511a4dad4923546c51365bf1a803774_duplicateId"
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."duplicateId" AS "bd93d5747511a4dad4923546c51365bf1a803774_duplicateId",
|
||||
"AssetEntity__AssetEntity_files"."id" AS "AssetEntity__AssetEntity_files_id",
|
||||
"AssetEntity__AssetEntity_files"."assetId" AS "AssetEntity__AssetEntity_files_assetId",
|
||||
"AssetEntity__AssetEntity_files"."createdAt" AS "AssetEntity__AssetEntity_files_createdAt",
|
||||
"AssetEntity__AssetEntity_files"."updatedAt" AS "AssetEntity__AssetEntity_files_updatedAt",
|
||||
"AssetEntity__AssetEntity_files"."type" AS "AssetEntity__AssetEntity_files_type",
|
||||
"AssetEntity__AssetEntity_files"."path" AS "AssetEntity__AssetEntity_files_path"
|
||||
FROM
|
||||
"assets" "AssetEntity"
|
||||
LEFT JOIN "exif" "AssetEntity__AssetEntity_exifInfo" ON "AssetEntity__AssetEntity_exifInfo"."assetId" = "AssetEntity"."id"
|
||||
|
@ -248,6 +252,7 @@ FROM
|
|||
LEFT JOIN "person" "8258e303a73a72cf6abb13d73fb592dde0d68280" ON "8258e303a73a72cf6abb13d73fb592dde0d68280"."id" = "AssetEntity__AssetEntity_faces"."personId"
|
||||
LEFT JOIN "asset_stack" "AssetEntity__AssetEntity_stack" ON "AssetEntity__AssetEntity_stack"."id" = "AssetEntity"."stackId"
|
||||
LEFT JOIN "assets" "bd93d5747511a4dad4923546c51365bf1a803774" ON "bd93d5747511a4dad4923546c51365bf1a803774"."stackId" = "AssetEntity__AssetEntity_stack"."id"
|
||||
LEFT JOIN "asset_files" "AssetEntity__AssetEntity_files" ON "AssetEntity__AssetEntity_files"."assetId" = "AssetEntity"."id"
|
||||
WHERE
|
||||
(("AssetEntity"."id" IN ($1)))
|
||||
|
||||
|
@ -298,8 +303,6 @@ FROM
|
|||
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||
"AssetEntity"."type" AS "AssetEntity_type",
|
||||
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
|
||||
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
|
||||
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||
|
@ -397,8 +400,6 @@ SELECT
|
|||
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||
"AssetEntity"."type" AS "AssetEntity_type",
|
||||
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
|
||||
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
|
||||
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||
|
@ -452,8 +453,6 @@ SELECT
|
|||
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||
"AssetEntity"."type" AS "AssetEntity_type",
|
||||
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
|
||||
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
|
||||
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||
|
@ -525,8 +524,6 @@ SELECT
|
|||
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||
"AssetEntity"."type" AS "AssetEntity_type",
|
||||
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
|
||||
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
|
||||
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||
|
@ -581,8 +578,6 @@ SELECT
|
|||
"asset"."deviceId" AS "asset_deviceId",
|
||||
"asset"."type" AS "asset_type",
|
||||
"asset"."originalPath" AS "asset_originalPath",
|
||||
"asset"."previewPath" AS "asset_previewPath",
|
||||
"asset"."thumbnailPath" AS "asset_thumbnailPath",
|
||||
"asset"."thumbhash" AS "asset_thumbhash",
|
||||
"asset"."encodedVideoPath" AS "asset_encodedVideoPath",
|
||||
"asset"."createdAt" AS "asset_createdAt",
|
||||
|
@ -603,6 +598,12 @@ SELECT
|
|||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||
"asset"."stackId" AS "asset_stackId",
|
||||
"asset"."duplicateId" AS "asset_duplicateId",
|
||||
"files"."id" AS "files_id",
|
||||
"files"."assetId" AS "files_assetId",
|
||||
"files"."createdAt" AS "files_createdAt",
|
||||
"files"."updatedAt" AS "files_updatedAt",
|
||||
"files"."type" AS "files_type",
|
||||
"files"."path" AS "files_path",
|
||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||
"exifInfo"."description" AS "exifInfo_description",
|
||||
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||
|
@ -642,8 +643,6 @@ SELECT
|
|||
"stackedAssets"."deviceId" AS "stackedAssets_deviceId",
|
||||
"stackedAssets"."type" AS "stackedAssets_type",
|
||||
"stackedAssets"."originalPath" AS "stackedAssets_originalPath",
|
||||
"stackedAssets"."previewPath" AS "stackedAssets_previewPath",
|
||||
"stackedAssets"."thumbnailPath" AS "stackedAssets_thumbnailPath",
|
||||
"stackedAssets"."thumbhash" AS "stackedAssets_thumbhash",
|
||||
"stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath",
|
||||
"stackedAssets"."createdAt" AS "stackedAssets_createdAt",
|
||||
|
@ -666,6 +665,7 @@ SELECT
|
|||
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
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"
|
||||
|
@ -692,6 +692,7 @@ SELECT
|
|||
)::timestamptz AS "timeBucket"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
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"
|
||||
|
@ -723,8 +724,6 @@ SELECT
|
|||
"asset"."deviceId" AS "asset_deviceId",
|
||||
"asset"."type" AS "asset_type",
|
||||
"asset"."originalPath" AS "asset_originalPath",
|
||||
"asset"."previewPath" AS "asset_previewPath",
|
||||
"asset"."thumbnailPath" AS "asset_thumbnailPath",
|
||||
"asset"."thumbhash" AS "asset_thumbhash",
|
||||
"asset"."encodedVideoPath" AS "asset_encodedVideoPath",
|
||||
"asset"."createdAt" AS "asset_createdAt",
|
||||
|
@ -745,6 +744,12 @@ SELECT
|
|||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||
"asset"."stackId" AS "asset_stackId",
|
||||
"asset"."duplicateId" AS "asset_duplicateId",
|
||||
"files"."id" AS "files_id",
|
||||
"files"."assetId" AS "files_assetId",
|
||||
"files"."createdAt" AS "files_createdAt",
|
||||
"files"."updatedAt" AS "files_updatedAt",
|
||||
"files"."type" AS "files_type",
|
||||
"files"."path" AS "files_path",
|
||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||
"exifInfo"."description" AS "exifInfo_description",
|
||||
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||
|
@ -784,8 +789,6 @@ SELECT
|
|||
"stackedAssets"."deviceId" AS "stackedAssets_deviceId",
|
||||
"stackedAssets"."type" AS "stackedAssets_type",
|
||||
"stackedAssets"."originalPath" AS "stackedAssets_originalPath",
|
||||
"stackedAssets"."previewPath" AS "stackedAssets_previewPath",
|
||||
"stackedAssets"."thumbnailPath" AS "stackedAssets_thumbnailPath",
|
||||
"stackedAssets"."thumbhash" AS "stackedAssets_thumbhash",
|
||||
"stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath",
|
||||
"stackedAssets"."createdAt" AS "stackedAssets_createdAt",
|
||||
|
@ -808,6 +811,7 @@ SELECT
|
|||
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
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"
|
||||
|
@ -841,8 +845,6 @@ SELECT
|
|||
"asset"."deviceId" AS "asset_deviceId",
|
||||
"asset"."type" AS "asset_type",
|
||||
"asset"."originalPath" AS "asset_originalPath",
|
||||
"asset"."previewPath" AS "asset_previewPath",
|
||||
"asset"."thumbnailPath" AS "asset_thumbnailPath",
|
||||
"asset"."thumbhash" AS "asset_thumbhash",
|
||||
"asset"."encodedVideoPath" AS "asset_encodedVideoPath",
|
||||
"asset"."createdAt" AS "asset_createdAt",
|
||||
|
@ -863,6 +865,12 @@ SELECT
|
|||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||
"asset"."stackId" AS "asset_stackId",
|
||||
"asset"."duplicateId" AS "asset_duplicateId",
|
||||
"files"."id" AS "files_id",
|
||||
"files"."assetId" AS "files_assetId",
|
||||
"files"."createdAt" AS "files_createdAt",
|
||||
"files"."updatedAt" AS "files_updatedAt",
|
||||
"files"."type" AS "files_type",
|
||||
"files"."path" AS "files_path",
|
||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||
"exifInfo"."description" AS "exifInfo_description",
|
||||
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||
|
@ -902,8 +910,6 @@ SELECT
|
|||
"stackedAssets"."deviceId" AS "stackedAssets_deviceId",
|
||||
"stackedAssets"."type" AS "stackedAssets_type",
|
||||
"stackedAssets"."originalPath" AS "stackedAssets_originalPath",
|
||||
"stackedAssets"."previewPath" AS "stackedAssets_previewPath",
|
||||
"stackedAssets"."thumbnailPath" AS "stackedAssets_thumbnailPath",
|
||||
"stackedAssets"."thumbhash" AS "stackedAssets_thumbhash",
|
||||
"stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath",
|
||||
"stackedAssets"."createdAt" AS "stackedAssets_createdAt",
|
||||
|
@ -926,6 +932,7 @@ SELECT
|
|||
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
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"
|
||||
|
@ -957,6 +964,7 @@ SELECT DISTINCT
|
|||
c.city AS "value"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
INNER JOIN "exif" "e" ON "asset"."id" = e."assetId"
|
||||
INNER JOIN "cities" "c" ON c.city = "e"."city"
|
||||
WHERE
|
||||
|
@ -987,6 +995,7 @@ SELECT DISTINCT
|
|||
unnest("si"."tags") AS "value"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
INNER JOIN "smart_info" "si" ON "asset"."id" = si."assetId"
|
||||
INNER JOIN "random_tags" "t" ON "si"."tags" @> ARRAY[t.tag]
|
||||
WHERE
|
||||
|
@ -1009,8 +1018,6 @@ SELECT
|
|||
"asset"."deviceId" AS "asset_deviceId",
|
||||
"asset"."type" AS "asset_type",
|
||||
"asset"."originalPath" AS "asset_originalPath",
|
||||
"asset"."previewPath" AS "asset_previewPath",
|
||||
"asset"."thumbnailPath" AS "asset_thumbnailPath",
|
||||
"asset"."thumbhash" AS "asset_thumbhash",
|
||||
"asset"."encodedVideoPath" AS "asset_encodedVideoPath",
|
||||
"asset"."createdAt" AS "asset_createdAt",
|
||||
|
@ -1031,6 +1038,12 @@ SELECT
|
|||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||
"asset"."stackId" AS "asset_stackId",
|
||||
"asset"."duplicateId" AS "asset_duplicateId",
|
||||
"files"."id" AS "files_id",
|
||||
"files"."assetId" AS "files_assetId",
|
||||
"files"."createdAt" AS "files_createdAt",
|
||||
"files"."updatedAt" AS "files_updatedAt",
|
||||
"files"."type" AS "files_type",
|
||||
"files"."path" AS "files_path",
|
||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||
"exifInfo"."description" AS "exifInfo_description",
|
||||
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||
|
@ -1065,6 +1078,7 @@ SELECT
|
|||
"stack"."primaryAssetId" AS "stack_primaryAssetId"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
|
||||
LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId"
|
||||
WHERE
|
||||
|
@ -1086,8 +1100,6 @@ SELECT
|
|||
"asset"."deviceId" AS "asset_deviceId",
|
||||
"asset"."type" AS "asset_type",
|
||||
"asset"."originalPath" AS "asset_originalPath",
|
||||
"asset"."previewPath" AS "asset_previewPath",
|
||||
"asset"."thumbnailPath" AS "asset_thumbnailPath",
|
||||
"asset"."thumbhash" AS "asset_thumbhash",
|
||||
"asset"."encodedVideoPath" AS "asset_encodedVideoPath",
|
||||
"asset"."createdAt" AS "asset_createdAt",
|
||||
|
@ -1108,6 +1120,12 @@ SELECT
|
|||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||
"asset"."stackId" AS "asset_stackId",
|
||||
"asset"."duplicateId" AS "asset_duplicateId",
|
||||
"files"."id" AS "files_id",
|
||||
"files"."assetId" AS "files_assetId",
|
||||
"files"."createdAt" AS "files_createdAt",
|
||||
"files"."updatedAt" AS "files_updatedAt",
|
||||
"files"."type" AS "files_type",
|
||||
"files"."path" AS "files_path",
|
||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||
"exifInfo"."description" AS "exifInfo_description",
|
||||
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||
|
@ -1142,9 +1160,34 @@ SELECT
|
|||
"stack"."primaryAssetId" AS "stack_primaryAssetId"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
|
||||
LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId"
|
||||
WHERE
|
||||
"asset"."isVisible" = true
|
||||
AND "asset"."ownerId" IN ($1)
|
||||
AND "asset"."updatedAt" > $2
|
||||
|
||||
-- AssetRepository.upsertFile
|
||||
INSERT INTO
|
||||
"asset_files" (
|
||||
"id",
|
||||
"assetId",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"type",
|
||||
"path"
|
||||
)
|
||||
VALUES
|
||||
(DEFAULT, $1, DEFAULT, DEFAULT, $2, $3)
|
||||
ON CONFLICT ("assetId", "type") DO
|
||||
UPDATE
|
||||
SET
|
||||
"assetId" = EXCLUDED."assetId",
|
||||
"type" = EXCLUDED."type",
|
||||
"path" = EXCLUDED."path",
|
||||
"updatedAt" = DEFAULT
|
||||
RETURNING
|
||||
"id",
|
||||
"createdAt",
|
||||
"updatedAt"
|
||||
|
|
|
@ -157,8 +157,6 @@ FROM
|
|||
"AssetFaceEntity__AssetFaceEntity_asset"."deviceId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceId",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."type" AS "AssetFaceEntity__AssetFaceEntity_asset_type",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."originalPath" AS "AssetFaceEntity__AssetFaceEntity_asset_originalPath",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."previewPath" AS "AssetFaceEntity__AssetFaceEntity_asset_previewPath",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbnailPath",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."thumbhash" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbhash",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."encodedVideoPath" AS "AssetFaceEntity__AssetFaceEntity_asset_encodedVideoPath",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_asset_createdAt",
|
||||
|
@ -255,8 +253,6 @@ FROM
|
|||
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||
"AssetEntity"."type" AS "AssetEntity_type",
|
||||
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
|
||||
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
|
||||
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||
|
@ -386,8 +382,6 @@ SELECT
|
|||
"AssetFaceEntity__AssetFaceEntity_asset"."deviceId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceId",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."type" AS "AssetFaceEntity__AssetFaceEntity_asset_type",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."originalPath" AS "AssetFaceEntity__AssetFaceEntity_asset_originalPath",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."previewPath" AS "AssetFaceEntity__AssetFaceEntity_asset_previewPath",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbnailPath",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."thumbhash" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbhash",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."encodedVideoPath" AS "AssetFaceEntity__AssetFaceEntity_asset_encodedVideoPath",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_asset_createdAt",
|
||||
|
|
|
@ -14,8 +14,6 @@ FROM
|
|||
"asset"."deviceId" AS "asset_deviceId",
|
||||
"asset"."type" AS "asset_type",
|
||||
"asset"."originalPath" AS "asset_originalPath",
|
||||
"asset"."previewPath" AS "asset_previewPath",
|
||||
"asset"."thumbnailPath" AS "asset_thumbnailPath",
|
||||
"asset"."thumbhash" AS "asset_thumbhash",
|
||||
"asset"."encodedVideoPath" AS "asset_encodedVideoPath",
|
||||
"asset"."createdAt" AS "asset_createdAt",
|
||||
|
@ -46,8 +44,6 @@ FROM
|
|||
"stackedAssets"."deviceId" AS "stackedAssets_deviceId",
|
||||
"stackedAssets"."type" AS "stackedAssets_type",
|
||||
"stackedAssets"."originalPath" AS "stackedAssets_originalPath",
|
||||
"stackedAssets"."previewPath" AS "stackedAssets_previewPath",
|
||||
"stackedAssets"."thumbnailPath" AS "stackedAssets_thumbnailPath",
|
||||
"stackedAssets"."thumbhash" AS "stackedAssets_thumbhash",
|
||||
"stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath",
|
||||
"stackedAssets"."createdAt" AS "stackedAssets_createdAt",
|
||||
|
@ -111,8 +107,6 @@ SELECT
|
|||
"asset"."deviceId" AS "asset_deviceId",
|
||||
"asset"."type" AS "asset_type",
|
||||
"asset"."originalPath" AS "asset_originalPath",
|
||||
"asset"."previewPath" AS "asset_previewPath",
|
||||
"asset"."thumbnailPath" AS "asset_thumbnailPath",
|
||||
"asset"."thumbhash" AS "asset_thumbhash",
|
||||
"asset"."encodedVideoPath" AS "asset_encodedVideoPath",
|
||||
"asset"."createdAt" AS "asset_createdAt",
|
||||
|
@ -143,8 +137,6 @@ SELECT
|
|||
"stackedAssets"."deviceId" AS "stackedAssets_deviceId",
|
||||
"stackedAssets"."type" AS "stackedAssets_type",
|
||||
"stackedAssets"."originalPath" AS "stackedAssets_originalPath",
|
||||
"stackedAssets"."previewPath" AS "stackedAssets_previewPath",
|
||||
"stackedAssets"."thumbnailPath" AS "stackedAssets_thumbnailPath",
|
||||
"stackedAssets"."thumbhash" AS "stackedAssets_thumbhash",
|
||||
"stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath",
|
||||
"stackedAssets"."createdAt" AS "stackedAssets_createdAt",
|
||||
|
@ -353,8 +345,6 @@ SELECT
|
|||
"asset"."deviceId" AS "asset_deviceId",
|
||||
"asset"."type" AS "asset_type",
|
||||
"asset"."originalPath" AS "asset_originalPath",
|
||||
"asset"."previewPath" AS "asset_previewPath",
|
||||
"asset"."thumbnailPath" AS "asset_thumbnailPath",
|
||||
"asset"."thumbhash" AS "asset_thumbhash",
|
||||
"asset"."encodedVideoPath" AS "asset_encodedVideoPath",
|
||||
"asset"."createdAt" AS "asset_createdAt",
|
||||
|
|
|
@ -28,8 +28,6 @@ FROM
|
|||
"SharedLinkEntity__SharedLinkEntity_assets"."deviceId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceId",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."type" AS "SharedLinkEntity__SharedLinkEntity_assets_type",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."originalPath" AS "SharedLinkEntity__SharedLinkEntity_assets_originalPath",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."previewPath" AS "SharedLinkEntity__SharedLinkEntity_assets_previewPath",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."thumbnailPath" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbnailPath",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."thumbhash" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbhash",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."encodedVideoPath" AS "SharedLinkEntity__SharedLinkEntity_assets_encodedVideoPath",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_assets_createdAt",
|
||||
|
@ -96,8 +94,6 @@ FROM
|
|||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deviceId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_deviceId",
|
||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."type" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_type",
|
||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."originalPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_originalPath",
|
||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."previewPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_previewPath",
|
||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."thumbnailPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_thumbnailPath",
|
||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."thumbhash" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_thumbhash",
|
||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."encodedVideoPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_encodedVideoPath",
|
||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."createdAt" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_createdAt",
|
||||
|
@ -218,8 +214,6 @@ SELECT
|
|||
"SharedLinkEntity__SharedLinkEntity_assets"."deviceId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceId",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."type" AS "SharedLinkEntity__SharedLinkEntity_assets_type",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."originalPath" AS "SharedLinkEntity__SharedLinkEntity_assets_originalPath",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."previewPath" AS "SharedLinkEntity__SharedLinkEntity_assets_previewPath",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."thumbnailPath" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbnailPath",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."thumbhash" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbhash",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."encodedVideoPath" AS "SharedLinkEntity__SharedLinkEntity_assets_encodedVideoPath",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_assets_createdAt",
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { ExifEntity } from 'src/entities/exif.entity';
|
||||
import { SmartInfoEntity } from 'src/entities/smart-info.entity';
|
||||
import { AssetOrder, AssetType } from 'src/enum';
|
||||
import { AssetFileType, AssetOrder, AssetType } from 'src/enum';
|
||||
import {
|
||||
AssetBuilderOptions,
|
||||
AssetCreate,
|
||||
|
@ -59,6 +60,7 @@ const dateTrunc = (options: TimeBucketOptions) =>
|
|||
export class AssetRepository implements IAssetRepository {
|
||||
constructor(
|
||||
@InjectRepository(AssetEntity) private repository: Repository<AssetEntity>,
|
||||
@InjectRepository(AssetFileEntity) private fileRepository: Repository<AssetFileEntity>,
|
||||
@InjectRepository(ExifEntity) private exifRepository: Repository<ExifEntity>,
|
||||
@InjectRepository(AssetJobStatusEntity) private jobStatusRepository: Repository<AssetJobStatusEntity>,
|
||||
@InjectRepository(SmartInfoEntity) private smartInfoRepository: Repository<SmartInfoEntity>,
|
||||
|
@ -84,7 +86,6 @@ export class AssetRepository implements IAssetRepository {
|
|||
`entity.ownerId IN (:...ownerIds)
|
||||
AND entity.isVisible = true
|
||||
AND entity.isArchived = false
|
||||
AND entity.previewPath IS NOT NULL
|
||||
AND EXTRACT(DAY FROM entity.localDateTime AT TIME ZONE 'UTC') = :day
|
||||
AND EXTRACT(MONTH FROM entity.localDateTime AT TIME ZONE 'UTC') = :month`,
|
||||
{
|
||||
|
@ -94,6 +95,7 @@ export class AssetRepository implements IAssetRepository {
|
|||
},
|
||||
)
|
||||
.leftJoinAndSelect('entity.exifInfo', 'exifInfo')
|
||||
.leftJoinAndSelect('entity.files', 'files')
|
||||
.orderBy('entity.localDateTime', 'ASC')
|
||||
.getMany();
|
||||
}
|
||||
|
@ -128,6 +130,7 @@ export class AssetRepository implements IAssetRepository {
|
|||
stack: {
|
||||
assets: true,
|
||||
},
|
||||
files: true,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
|
@ -214,7 +217,7 @@ export class AssetRepository implements IAssetRepository {
|
|||
}
|
||||
|
||||
getAll(pagination: PaginationOptions, options: AssetSearchOptions = {}): Paginated<AssetEntity> {
|
||||
let builder = this.repository.createQueryBuilder('asset');
|
||||
let builder = this.repository.createQueryBuilder('asset').leftJoinAndSelect('asset.files', 'files');
|
||||
builder = searchAssetBuilder(builder, options);
|
||||
builder.orderBy('asset.createdAt', options.orderDirection ?? 'ASC');
|
||||
return paginatedBuilder<AssetEntity>(builder, {
|
||||
|
@ -706,7 +709,11 @@ export class AssetRepository implements IAssetRepository {
|
|||
}
|
||||
|
||||
private getBuilder(options: AssetBuilderOptions) {
|
||||
const builder = this.repository.createQueryBuilder('asset').where('asset.isVisible = true');
|
||||
const builder = this.repository
|
||||
.createQueryBuilder('asset')
|
||||
.where('asset.isVisible = true')
|
||||
.leftJoinAndSelect('asset.files', 'files');
|
||||
|
||||
if (options.assetType !== undefined) {
|
||||
builder.andWhere('asset.type = :assetType', { assetType: options.assetType });
|
||||
}
|
||||
|
@ -812,4 +819,9 @@ export class AssetRepository implements IAssetRepository {
|
|||
.withDeleted();
|
||||
return builder.getMany();
|
||||
}
|
||||
|
||||
@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'] });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { BadRequestException, NotFoundException, UnauthorizedException } from '@
|
|||
import { Stats } from 'node:fs';
|
||||
import { AssetMediaStatus, AssetRejectReason, AssetUploadAction } from 'src/dtos/asset-media-response.dto';
|
||||
import { AssetMediaCreateDto, AssetMediaReplaceDto, UploadFieldName } from 'src/dtos/asset-media.dto';
|
||||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||
import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity } from 'src/entities/asset.entity';
|
||||
import { AssetType } from 'src/enum';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
|
@ -150,15 +151,14 @@ const assetEntity = Object.freeze({
|
|||
deviceId: 'device_id_1',
|
||||
type: AssetType.VIDEO,
|
||||
originalPath: 'fake_path/asset_1.jpeg',
|
||||
previewPath: '',
|
||||
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
updatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
isFavorite: false,
|
||||
isArchived: false,
|
||||
thumbnailPath: '',
|
||||
encodedVideoPath: '',
|
||||
duration: '0:00:00.000000',
|
||||
files: [] as AssetFileEntity[],
|
||||
exifInfo: {
|
||||
latitude: 49.533_547,
|
||||
longitude: 10.703_075,
|
||||
|
@ -418,7 +418,7 @@ describe(AssetMediaService.name, () => {
|
|||
|
||||
await expect(sut.downloadOriginal(authStub.admin, 'asset-1')).rejects.toBeInstanceOf(NotFoundException);
|
||||
|
||||
expect(assetMock.getById).toHaveBeenCalledWith('asset-1');
|
||||
expect(assetMock.getById).toHaveBeenCalledWith('asset-1', { files: true });
|
||||
});
|
||||
|
||||
it('should download a file', async () => {
|
||||
|
|
|
@ -36,6 +36,7 @@ import { IJobRepository, JobName } from 'src/interfaces/job.interface';
|
|||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { CacheControl, ImmichFileResponse } from 'src/utils/file';
|
||||
import { mimeTypes } from 'src/utils/mime-types';
|
||||
import { fromChecksum } from 'src/utils/request';
|
||||
|
@ -238,9 +239,10 @@ export class AssetMediaService {
|
|||
const asset = await this.findOrFail(id);
|
||||
const size = dto.size ?? AssetMediaSize.THUMBNAIL;
|
||||
|
||||
let filepath = asset.previewPath;
|
||||
if (size === AssetMediaSize.THUMBNAIL && asset.thumbnailPath) {
|
||||
filepath = asset.thumbnailPath;
|
||||
const { thumbnailFile, previewFile } = getAssetFiles(asset.files);
|
||||
let filepath = previewFile?.path;
|
||||
if (size === AssetMediaSize.THUMBNAIL && thumbnailFile) {
|
||||
filepath = thumbnailFile.path;
|
||||
}
|
||||
|
||||
if (!filepath) {
|
||||
|
@ -460,7 +462,7 @@ export class AssetMediaService {
|
|||
}
|
||||
|
||||
private async findOrFail(id: string): Promise<AssetEntity> {
|
||||
const asset = await this.assetRepository.getById(id);
|
||||
const asset = await this.assetRepository.getById(id, { files: true });
|
||||
if (!asset) {
|
||||
throw new NotFoundException('Asset not found');
|
||||
}
|
||||
|
|
|
@ -299,8 +299,8 @@ describe(AssetService.name, () => {
|
|||
name: JobName.DELETE_FILES,
|
||||
data: {
|
||||
files: [
|
||||
assetWithFace.thumbnailPath,
|
||||
assetWithFace.previewPath,
|
||||
'/uploads/user-id/webp/path.ext',
|
||||
'/uploads/user-id/thumbs/path.jpg',
|
||||
assetWithFace.encodedVideoPath,
|
||||
assetWithFace.sidecarPath,
|
||||
assetWithFace.originalPath,
|
||||
|
|
|
@ -39,7 +39,7 @@ import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
|||
import { IStackRepository } from 'src/interfaces/stack.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { getMyPartnerIds } from 'src/utils/asset.util';
|
||||
import { getAssetFiles, getMyPartnerIds } from 'src/utils/asset.util';
|
||||
import { usePagination } from 'src/utils/pagination';
|
||||
|
||||
export class AssetService {
|
||||
|
@ -71,9 +71,10 @@ export class AssetService {
|
|||
const userIds = [auth.user.id, ...partnerIds];
|
||||
|
||||
const assets = await this.assetRepository.getByDayOfYear(userIds, dto);
|
||||
const assetsWithThumbnails = assets.filter(({ files }) => !!getAssetFiles(files).thumbnailFile);
|
||||
const groups: Record<number, AssetEntity[]> = {};
|
||||
const currentYear = new Date().getFullYear();
|
||||
for (const asset of assets) {
|
||||
for (const asset of assetsWithThumbnails) {
|
||||
const yearsAgo = currentYear - asset.localDateTime.getFullYear();
|
||||
if (!groups[yearsAgo]) {
|
||||
groups[yearsAgo] = [];
|
||||
|
@ -126,6 +127,7 @@ export class AssetService {
|
|||
exifInfo: true,
|
||||
},
|
||||
},
|
||||
files: true,
|
||||
},
|
||||
{
|
||||
faces: {
|
||||
|
@ -170,6 +172,7 @@ export class AssetService {
|
|||
faces: {
|
||||
person: true,
|
||||
},
|
||||
files: true,
|
||||
});
|
||||
if (!asset) {
|
||||
throw new BadRequestException('Asset not found');
|
||||
|
@ -223,6 +226,7 @@ export class AssetService {
|
|||
library: true,
|
||||
stack: { assets: true },
|
||||
exifInfo: true,
|
||||
files: true,
|
||||
});
|
||||
|
||||
if (!asset) {
|
||||
|
@ -260,7 +264,8 @@ export class AssetService {
|
|||
}
|
||||
}
|
||||
|
||||
const files = [asset.thumbnailPath, asset.previewPath, asset.encodedVideoPath];
|
||||
const { thumbnailFile, previewFile } = getAssetFiles(asset.files);
|
||||
const files = [thumbnailFile?.path, previewFile?.path, asset.encodedVideoPath];
|
||||
if (deleteOnDisk) {
|
||||
files.push(asset.sidecarPath, asset.originalPath);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
} from 'src/dtos/audit.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { AssetPathType, PersonPathType, UserPathType } from 'src/entities/move.entity';
|
||||
import { DatabaseAction, Permission } from 'src/enum';
|
||||
import { AssetFileType, DatabaseAction, Permission } from 'src/enum';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { IAuditRepository } from 'src/interfaces/audit.interface';
|
||||
|
@ -24,6 +24,7 @@ import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
|||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { usePagination } from 'src/utils/pagination';
|
||||
|
||||
@Injectable()
|
||||
|
@ -97,12 +98,12 @@ export class AuditService {
|
|||
}
|
||||
|
||||
case AssetPathType.PREVIEW: {
|
||||
await this.assetRepository.update({ id, previewPath: pathValue });
|
||||
await this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.PREVIEW, path: pathValue });
|
||||
break;
|
||||
}
|
||||
|
||||
case AssetPathType.THUMBNAIL: {
|
||||
await this.assetRepository.update({ id, thumbnailPath: pathValue });
|
||||
await this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.THUMBNAIL, path: pathValue });
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -155,7 +156,7 @@ export class AuditService {
|
|||
}
|
||||
}
|
||||
|
||||
const track = (filename: string | null) => {
|
||||
const track = (filename: string | null | undefined) => {
|
||||
if (!filename) {
|
||||
return;
|
||||
}
|
||||
|
@ -175,8 +176,9 @@ export class AuditService {
|
|||
const orphans: FileReportItemDto[] = [];
|
||||
for await (const assets of pagination) {
|
||||
assetCount += assets.length;
|
||||
for (const { id, originalPath, previewPath, encodedVideoPath, thumbnailPath, isExternal, checksum } of assets) {
|
||||
for (const file of [originalPath, previewPath, encodedVideoPath, thumbnailPath]) {
|
||||
for (const { id, files, originalPath, encodedVideoPath, isExternal, checksum } of assets) {
|
||||
const { previewFile, thumbnailFile } = getAssetFiles(files);
|
||||
for (const file of [originalPath, previewFile?.path, encodedVideoPath, thumbnailFile?.path]) {
|
||||
track(file);
|
||||
}
|
||||
|
||||
|
@ -192,11 +194,11 @@ export class AuditService {
|
|||
) {
|
||||
orphans.push({ ...entity, pathType: AssetPathType.ORIGINAL, pathValue: originalPath });
|
||||
}
|
||||
if (previewPath && !hasFile(thumbFiles, previewPath)) {
|
||||
orphans.push({ ...entity, pathType: AssetPathType.PREVIEW, pathValue: previewPath });
|
||||
if (previewFile && !hasFile(thumbFiles, previewFile.path)) {
|
||||
orphans.push({ ...entity, pathType: AssetPathType.PREVIEW, pathValue: previewFile.path });
|
||||
}
|
||||
if (thumbnailPath && !hasFile(thumbFiles, thumbnailPath)) {
|
||||
orphans.push({ ...entity, pathType: AssetPathType.THUMBNAIL, pathValue: thumbnailPath });
|
||||
if (thumbnailFile && !hasFile(thumbFiles, thumbnailFile.path)) {
|
||||
orphans.push({ ...entity, pathType: AssetPathType.THUMBNAIL, pathValue: thumbnailFile.path });
|
||||
}
|
||||
if (encodedVideoPath && !hasFile(videoFiles, encodedVideoPath)) {
|
||||
orphans.push({ ...entity, pathType: AssetPathType.THUMBNAIL, pathValue: encodedVideoPath });
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { AssetDuplicateResult, ISearchRepository } from 'src/interfaces/search.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { isDuplicateDetectionEnabled } from 'src/utils/misc';
|
||||
import { usePagination } from 'src/utils/pagination';
|
||||
|
||||
|
@ -69,7 +70,7 @@ export class DuplicateService {
|
|||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
const asset = await this.assetRepository.getById(id, { smartSearch: true });
|
||||
const asset = await this.assetRepository.getById(id, { files: true, smartSearch: true });
|
||||
if (!asset) {
|
||||
this.logger.error(`Asset ${id} not found`);
|
||||
return JobStatus.FAILED;
|
||||
|
@ -80,7 +81,8 @@ export class DuplicateService {
|
|||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
if (!asset.previewPath) {
|
||||
const { previewFile } = getAssetFiles(asset.files);
|
||||
if (!previewFile) {
|
||||
this.logger.warn(`Asset ${id} is missing preview image`);
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
VideoCodec,
|
||||
} from 'src/config';
|
||||
import { ExifEntity } from 'src/entities/exif.entity';
|
||||
import { AssetType } from 'src/enum';
|
||||
import { AssetFileType, AssetType } from 'src/enum';
|
||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||
|
@ -298,18 +298,20 @@ describe(MediaService.name, () => {
|
|||
colorspace: Colorspace.SRGB,
|
||||
processInvalidImages: false,
|
||||
});
|
||||
expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-id', previewPath });
|
||||
expect(assetMock.upsertFile).toHaveBeenCalledWith({
|
||||
assetId: 'asset-id',
|
||||
type: AssetFileType.PREVIEW,
|
||||
path: previewPath,
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete previous preview if different path', async () => {
|
||||
const previousPreviewPath = assetStub.image.previewPath;
|
||||
|
||||
systemMock.get.mockResolvedValue({ image: { thumbnailFormat: ImageFormat.WEBP } });
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||
|
||||
await sut.handleGeneratePreview({ id: assetStub.image.id });
|
||||
|
||||
expect(storageMock.unlink).toHaveBeenCalledWith(previousPreviewPath);
|
||||
expect(storageMock.unlink).toHaveBeenCalledWith('/uploads/user-id/thumbs/path.jpg');
|
||||
});
|
||||
|
||||
it('should generate a P3 thumbnail for a wide gamut image', async () => {
|
||||
|
@ -330,9 +332,10 @@ describe(MediaService.name, () => {
|
|||
processInvalidImages: false,
|
||||
},
|
||||
);
|
||||
expect(assetMock.update).toHaveBeenCalledWith({
|
||||
id: 'asset-id',
|
||||
previewPath: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
|
||||
expect(assetMock.upsertFile).toHaveBeenCalledWith({
|
||||
assetId: 'asset-id',
|
||||
type: AssetFileType.PREVIEW,
|
||||
path: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -357,9 +360,10 @@ describe(MediaService.name, () => {
|
|||
twoPass: false,
|
||||
},
|
||||
);
|
||||
expect(assetMock.update).toHaveBeenCalledWith({
|
||||
id: 'asset-id',
|
||||
previewPath: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
|
||||
expect(assetMock.upsertFile).toHaveBeenCalledWith({
|
||||
assetId: 'asset-id',
|
||||
type: AssetFileType.PREVIEW,
|
||||
path: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -384,9 +388,10 @@ describe(MediaService.name, () => {
|
|||
twoPass: false,
|
||||
},
|
||||
);
|
||||
expect(assetMock.update).toHaveBeenCalledWith({
|
||||
id: 'asset-id',
|
||||
previewPath: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
|
||||
expect(assetMock.upsertFile).toHaveBeenCalledWith({
|
||||
assetId: 'asset-id',
|
||||
type: AssetFileType.PREVIEW,
|
||||
path: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -472,19 +477,21 @@ describe(MediaService.name, () => {
|
|||
colorspace: Colorspace.SRGB,
|
||||
processInvalidImages: false,
|
||||
});
|
||||
expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-id', thumbnailPath });
|
||||
expect(assetMock.upsertFile).toHaveBeenCalledWith({
|
||||
assetId: 'asset-id',
|
||||
type: AssetFileType.THUMBNAIL,
|
||||
path: thumbnailPath,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it('should delete previous thumbnail if different path', async () => {
|
||||
const previousThumbnailPath = assetStub.image.thumbnailPath;
|
||||
|
||||
systemMock.get.mockResolvedValue({ image: { thumbnailFormat: ImageFormat.WEBP } });
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||
|
||||
await sut.handleGenerateThumbnail({ id: assetStub.image.id });
|
||||
|
||||
expect(storageMock.unlink).toHaveBeenCalledWith(previousThumbnailPath);
|
||||
expect(storageMock.unlink).toHaveBeenCalledWith('/uploads/user-id/webp/path.ext');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -504,9 +511,10 @@ describe(MediaService.name, () => {
|
|||
processInvalidImages: false,
|
||||
},
|
||||
);
|
||||
expect(assetMock.update).toHaveBeenCalledWith({
|
||||
id: 'asset-id',
|
||||
thumbnailPath: 'upload/thumbs/user-id/as/se/asset-id-thumbnail.webp',
|
||||
expect(assetMock.upsertFile).toHaveBeenCalledWith({
|
||||
assetId: 'asset-id',
|
||||
type: AssetFileType.THUMBNAIL,
|
||||
path: 'upload/thumbs/user-id/as/se/asset-id-thumbnail.webp',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import { SystemConfigCore } from 'src/cores/system-config.core';
|
|||
import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { AssetPathType } from 'src/entities/move.entity';
|
||||
import { AssetType } from 'src/enum';
|
||||
import { AssetFileType, AssetType } from 'src/enum';
|
||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
import {
|
||||
|
@ -34,6 +34,7 @@ import { IMoveRepository } from 'src/interfaces/move.interface';
|
|||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { BaseConfig, ThumbnailConfig } from 'src/utils/media';
|
||||
import { mimeTypes } from 'src/utils/mime-types';
|
||||
import { usePagination } from 'src/utils/pagination';
|
||||
|
@ -72,7 +73,11 @@ export class MediaService {
|
|||
async handleQueueGenerateThumbnails({ force }: IBaseJob): Promise<JobStatus> {
|
||||
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => {
|
||||
return force
|
||||
? this.assetRepository.getAll(pagination, { isVisible: true, withDeleted: true, withArchived: true })
|
||||
? this.assetRepository.getAll(pagination, {
|
||||
isVisible: true,
|
||||
withDeleted: true,
|
||||
withArchived: true,
|
||||
})
|
||||
: this.assetRepository.getWithout(pagination, WithoutProperty.THUMBNAIL);
|
||||
});
|
||||
|
||||
|
@ -80,13 +85,17 @@ export class MediaService {
|
|||
const jobs: JobItem[] = [];
|
||||
|
||||
for (const asset of assets) {
|
||||
if (!asset.previewPath || force) {
|
||||
const { previewFile, thumbnailFile } = getAssetFiles(asset.files);
|
||||
|
||||
if (!previewFile || force) {
|
||||
jobs.push({ name: JobName.GENERATE_PREVIEW, data: { id: asset.id } });
|
||||
continue;
|
||||
}
|
||||
if (!asset.thumbnailPath) {
|
||||
|
||||
if (!thumbnailFile) {
|
||||
jobs.push({ name: JobName.GENERATE_THUMBNAIL, data: { id: asset.id } });
|
||||
}
|
||||
|
||||
if (!asset.thumbhash) {
|
||||
jobs.push({ name: JobName.GENERATE_THUMBHASH, data: { id: asset.id } });
|
||||
}
|
||||
|
@ -152,7 +161,7 @@ export class MediaService {
|
|||
|
||||
async handleAssetMigration({ id }: IEntityJob): Promise<JobStatus> {
|
||||
const { image } = await this.configCore.getConfig({ withCache: true });
|
||||
const [asset] = await this.assetRepository.getByIds([id]);
|
||||
const [asset] = await this.assetRepository.getByIds([id], { files: true });
|
||||
if (!asset) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
@ -182,12 +191,14 @@ export class MediaService {
|
|||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
if (asset.previewPath && asset.previewPath !== previewPath) {
|
||||
const { previewFile } = getAssetFiles(asset.files);
|
||||
if (previewFile && previewFile.path !== previewPath) {
|
||||
this.logger.debug(`Deleting old preview for asset ${asset.id}`);
|
||||
await this.storageRepository.unlink(asset.previewPath);
|
||||
await this.storageRepository.unlink(previewFile.path);
|
||||
}
|
||||
|
||||
await this.assetRepository.update({ id: asset.id, previewPath });
|
||||
await this.assetRepository.upsertFile({ assetId: asset.id, type: AssetFileType.PREVIEW, path: previewPath });
|
||||
await this.assetRepository.update({ id: asset.id, updatedAt: new Date() });
|
||||
await this.assetRepository.upsertJobStatus({ assetId: asset.id, previewAt: new Date() });
|
||||
|
||||
return JobStatus.SUCCESS;
|
||||
|
@ -253,7 +264,7 @@ export class MediaService {
|
|||
async handleGenerateThumbnail({ id }: IEntityJob): Promise<JobStatus> {
|
||||
const [{ image }, [asset]] = await Promise.all([
|
||||
this.configCore.getConfig({ withCache: true }),
|
||||
this.assetRepository.getByIds([id], { exifInfo: true }),
|
||||
this.assetRepository.getByIds([id], { exifInfo: true, files: true }),
|
||||
]);
|
||||
if (!asset) {
|
||||
return JobStatus.FAILED;
|
||||
|
@ -268,19 +279,21 @@ export class MediaService {
|
|||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
if (asset.thumbnailPath && asset.thumbnailPath !== thumbnailPath) {
|
||||
const { thumbnailFile } = getAssetFiles(asset.files);
|
||||
if (thumbnailFile && thumbnailFile.path !== thumbnailPath) {
|
||||
this.logger.debug(`Deleting old thumbnail for asset ${asset.id}`);
|
||||
await this.storageRepository.unlink(asset.thumbnailPath);
|
||||
await this.storageRepository.unlink(thumbnailFile.path);
|
||||
}
|
||||
|
||||
await this.assetRepository.update({ id: asset.id, thumbnailPath });
|
||||
await this.assetRepository.upsertFile({ assetId: asset.id, type: AssetFileType.THUMBNAIL, path: thumbnailPath });
|
||||
await this.assetRepository.update({ id: asset.id, updatedAt: new Date() });
|
||||
await this.assetRepository.upsertJobStatus({ assetId: asset.id, thumbnailAt: new Date() });
|
||||
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
||||
async handleGenerateThumbhash({ id }: IEntityJob): Promise<JobStatus> {
|
||||
const [asset] = await this.assetRepository.getByIds([id]);
|
||||
const [asset] = await this.assetRepository.getByIds([id], { files: true });
|
||||
if (!asset) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
@ -289,11 +302,12 @@ export class MediaService {
|
|||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
if (!asset.previewPath) {
|
||||
const { previewFile } = getAssetFiles(asset.files);
|
||||
if (!previewFile) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
const thumbhash = await this.mediaRepository.generateThumbhash(asset.previewPath);
|
||||
const thumbhash = await this.mediaRepository.generateThumbhash(previewFile.path);
|
||||
await this.assetRepository.update({ id: asset.id, thumbhash });
|
||||
|
||||
return JobStatus.SUCCESS;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { defaults, SystemConfig } from 'src/config';
|
||||
import { AlbumUserEntity } from 'src/entities/album-user.entity';
|
||||
import { UserMetadataKey } from 'src/enum';
|
||||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||
import { AssetFileType, UserMetadataKey } from 'src/enum';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||
|
@ -333,7 +334,9 @@ describe(NotificationService.name, () => {
|
|||
notificationMock.renderEmail.mockResolvedValue({ html: '', text: '' });
|
||||
|
||||
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.SUCCESS);
|
||||
expect(assetMock.getById).toHaveBeenCalledWith(albumStub.emptyWithValidThumbnail.albumThumbnailAssetId);
|
||||
expect(assetMock.getById).toHaveBeenCalledWith(albumStub.emptyWithValidThumbnail.albumThumbnailAssetId, {
|
||||
files: true,
|
||||
});
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({
|
||||
name: JobName.SEND_EMAIL,
|
||||
data: expect.objectContaining({
|
||||
|
@ -358,10 +361,15 @@ describe(NotificationService.name, () => {
|
|||
});
|
||||
systemMock.get.mockResolvedValue({ server: {} });
|
||||
notificationMock.renderEmail.mockResolvedValue({ html: '', text: '' });
|
||||
assetMock.getById.mockResolvedValue({ ...assetStub.image, thumbnailPath: 'path-to-thumb.jpg' });
|
||||
assetMock.getById.mockResolvedValue({
|
||||
...assetStub.image,
|
||||
files: [{ assetId: 'asset-id', type: AssetFileType.THUMBNAIL, path: 'path-to-thumb.jpg' } as AssetFileEntity],
|
||||
});
|
||||
|
||||
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.SUCCESS);
|
||||
expect(assetMock.getById).toHaveBeenCalledWith(albumStub.emptyWithValidThumbnail.albumThumbnailAssetId);
|
||||
expect(assetMock.getById).toHaveBeenCalledWith(albumStub.emptyWithValidThumbnail.albumThumbnailAssetId, {
|
||||
files: true,
|
||||
});
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({
|
||||
name: JobName.SEND_EMAIL,
|
||||
data: expect.objectContaining({
|
||||
|
@ -389,7 +397,9 @@ describe(NotificationService.name, () => {
|
|||
assetMock.getById.mockResolvedValue(assetStub.image);
|
||||
|
||||
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.SUCCESS);
|
||||
expect(assetMock.getById).toHaveBeenCalledWith(albumStub.emptyWithValidThumbnail.albumThumbnailAssetId);
|
||||
expect(assetMock.getById).toHaveBeenCalledWith(albumStub.emptyWithValidThumbnail.albumThumbnailAssetId, {
|
||||
files: true,
|
||||
});
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({
|
||||
name: JobName.SEND_EMAIL,
|
||||
data: expect.objectContaining({
|
||||
|
|
|
@ -21,6 +21,7 @@ import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
|||
import { EmailImageAttachment, EmailTemplate, INotificationRepository } from 'src/interfaces/notification.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { getFilenameExtension } from 'src/utils/file';
|
||||
import { getPreferences } from 'src/utils/preferences';
|
||||
|
||||
|
@ -268,14 +269,15 @@ export class NotificationService {
|
|||
return;
|
||||
}
|
||||
|
||||
const albumThumbnail = await this.assetRepository.getById(album.albumThumbnailAssetId);
|
||||
if (!albumThumbnail?.thumbnailPath) {
|
||||
const albumThumbnail = await this.assetRepository.getById(album.albumThumbnailAssetId, { files: true });
|
||||
const { thumbnailFile } = getAssetFiles(albumThumbnail?.files);
|
||||
if (!thumbnailFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
filename: `album-thumbnail${getFilenameExtension(albumThumbnail.thumbnailPath)}`,
|
||||
path: albumThumbnail.thumbnailPath,
|
||||
filename: `album-thumbnail${getFilenameExtension(thumbnailFile.path)}`,
|
||||
path: thumbnailFile.path,
|
||||
cid: 'album-thumbnail',
|
||||
};
|
||||
}
|
||||
|
|
|
@ -716,7 +716,7 @@ describe(PersonService.name, () => {
|
|||
await sut.handleDetectFaces({ id: assetStub.image.id });
|
||||
expect(machineLearningMock.detectFaces).toHaveBeenCalledWith(
|
||||
'http://immich-machine-learning:3003',
|
||||
assetStub.image.previewPath,
|
||||
'/uploads/user-id/thumbs/path.jpg',
|
||||
expect.objectContaining({ minScore: 0.7, modelName: 'buffalo_l' }),
|
||||
);
|
||||
expect(personMock.createFaces).not.toHaveBeenCalled();
|
||||
|
@ -946,7 +946,7 @@ describe(PersonService.name, () => {
|
|||
|
||||
await sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id });
|
||||
|
||||
expect(assetMock.getById).toHaveBeenCalledWith(faceStub.middle.assetId, { exifInfo: true });
|
||||
expect(assetMock.getById).toHaveBeenCalledWith(faceStub.middle.assetId, { exifInfo: true, files: true });
|
||||
expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/admin_id/pe/rs');
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledWith(
|
||||
assetStub.primaryImage.originalPath,
|
||||
|
@ -1032,7 +1032,7 @@ describe(PersonService.name, () => {
|
|||
await sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id });
|
||||
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledWith(
|
||||
assetStub.video.previewPath,
|
||||
'/uploads/user-id/thumbs/path.jpg',
|
||||
'upload/thumbs/admin_id/pe/rs/person-1.jpeg',
|
||||
{
|
||||
format: 'jpeg',
|
||||
|
|
|
@ -50,6 +50,7 @@ import { IPersonRepository, UpdateFacesData } from 'src/interfaces/person.interf
|
|||
import { ISearchRepository } from 'src/interfaces/search.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { CacheControl, ImmichFileResponse } from 'src/utils/file';
|
||||
import { mimeTypes } from 'src/utils/mime-types';
|
||||
import { isFacialRecognitionEnabled } from 'src/utils/misc';
|
||||
|
@ -333,9 +334,11 @@ export class PersonService {
|
|||
faces: {
|
||||
person: false,
|
||||
},
|
||||
files: true,
|
||||
};
|
||||
const [asset] = await this.assetRepository.getByIds([id], relations);
|
||||
if (!asset || !asset.previewPath || asset.faces?.length > 0) {
|
||||
const { previewFile } = getAssetFiles(asset.files);
|
||||
if (!asset || !previewFile || asset.faces?.length > 0) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
|
@ -349,11 +352,11 @@ export class PersonService {
|
|||
|
||||
const { imageHeight, imageWidth, faces } = await this.machineLearningRepository.detectFaces(
|
||||
machineLearning.url,
|
||||
asset.previewPath,
|
||||
previewFile.path,
|
||||
machineLearning.facialRecognition,
|
||||
);
|
||||
|
||||
this.logger.debug(`${faces.length} faces detected in ${asset.previewPath}`);
|
||||
this.logger.debug(`${faces.length} faces detected in ${previewFile.path}`);
|
||||
|
||||
if (faces.length > 0) {
|
||||
await this.jobRepository.queue({ name: JobName.QUEUE_FACIAL_RECOGNITION, data: { force: false } });
|
||||
|
@ -549,7 +552,10 @@ export class PersonService {
|
|||
imageHeight: oldHeight,
|
||||
} = face;
|
||||
|
||||
const asset = await this.assetRepository.getById(assetId, { exifInfo: true });
|
||||
const asset = await this.assetRepository.getById(assetId, {
|
||||
exifInfo: true,
|
||||
files: true,
|
||||
});
|
||||
if (!asset) {
|
||||
this.logger.error(`Could not generate person thumbnail: asset ${assetId} does not exist`);
|
||||
return JobStatus.FAILED;
|
||||
|
@ -646,7 +652,8 @@ export class PersonService {
|
|||
throw new Error(`Asset ${asset.id} dimensions are unknown`);
|
||||
}
|
||||
|
||||
if (!asset.previewPath) {
|
||||
const { previewFile } = getAssetFiles(asset.files);
|
||||
if (!previewFile) {
|
||||
throw new Error(`Asset ${asset.id} has no preview path`);
|
||||
}
|
||||
|
||||
|
@ -659,8 +666,8 @@ export class PersonService {
|
|||
return { width, height, inputPath: asset.originalPath };
|
||||
}
|
||||
|
||||
const { width, height } = await this.mediaRepository.getImageDimensions(asset.previewPath);
|
||||
return { width, height, inputPath: asset.previewPath };
|
||||
const { width, height } = await this.mediaRepository.getImageDimensions(previewFile.path);
|
||||
return { width, height, inputPath: previewFile.path };
|
||||
}
|
||||
|
||||
private getCrop(dims: { old: ImageDimensions; new: ImageDimensions }, { x1, y1, x2, y2 }: BoundingBox): CropOptions {
|
||||
|
|
|
@ -318,7 +318,7 @@ describe(SmartInfoService.name, () => {
|
|||
|
||||
expect(machineMock.encodeImage).toHaveBeenCalledWith(
|
||||
'http://immich-machine-learning:3003',
|
||||
assetStub.image.previewPath,
|
||||
'/uploads/user-id/thumbs/path.jpg',
|
||||
expect.objectContaining({ modelName: 'ViT-B-32__openai' }),
|
||||
);
|
||||
expect(searchMock.upsert).toHaveBeenCalledWith(assetStub.image.id, [0.01, 0.02, 0.03]);
|
||||
|
|
|
@ -18,6 +18,7 @@ import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
|||
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
|
||||
import { ISearchRepository } from 'src/interfaces/search.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { getCLIPModelInfo, isSmartSearchEnabled } from 'src/utils/misc';
|
||||
import { usePagination } from 'src/utils/pagination';
|
||||
|
||||
|
@ -135,7 +136,7 @@ export class SmartInfoService {
|
|||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
const [asset] = await this.assetRepository.getByIds([id]);
|
||||
const [asset] = await this.assetRepository.getByIds([id], { files: true });
|
||||
if (!asset) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
@ -144,13 +145,14 @@ export class SmartInfoService {
|
|||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
if (!asset.previewPath) {
|
||||
const { previewFile } = getAssetFiles(asset.files);
|
||||
if (!previewFile) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
const embedding = await this.machineLearning.encodeImage(
|
||||
machineLearning.url,
|
||||
asset.previewPath,
|
||||
previewFile.path,
|
||||
machineLearning.clip,
|
||||
);
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { AccessCore } from 'src/cores/access.core';
|
||||
import { BulkIdErrorReason, BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { Permission } from 'src/enum';
|
||||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||
import { AssetFileType, Permission } from 'src/enum';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
||||
|
||||
|
@ -11,6 +12,15 @@ export interface IBulkAsset {
|
|||
removeAssetIds: (id: string, assetIds: string[]) => Promise<void>;
|
||||
}
|
||||
|
||||
const getFileByType = (files: AssetFileEntity[] | undefined, type: AssetFileType) => {
|
||||
return (files || []).find((file) => file.type === type);
|
||||
};
|
||||
|
||||
export const getAssetFiles = (files?: AssetFileEntity[]) => ({
|
||||
previewFile: getFileByType(files, AssetFileType.PREVIEW),
|
||||
thumbnailFile: getFileByType(files, AssetFileType.THUMBNAIL),
|
||||
});
|
||||
|
||||
export const addAssets = async (
|
||||
auth: AuthDto,
|
||||
repositories: { accessRepository: IAccessRepository; repository: IBulkAsset },
|
||||
|
|
|
@ -71,7 +71,7 @@ export function searchAssetBuilder(
|
|||
builder.andWhere(`${builder.alias}.ownerId IN (:...userIds)`, { userIds: options.userIds });
|
||||
}
|
||||
|
||||
const path = _.pick(options, ['encodedVideoPath', 'originalPath', 'previewPath', 'thumbnailPath']);
|
||||
const path = _.pick(options, ['encodedVideoPath', 'originalPath']);
|
||||
builder.andWhere(_.omitBy(path, _.isUndefined));
|
||||
|
||||
if (options.originalFileName) {
|
||||
|
|
111
server/test/fixtures/asset.stub.ts
vendored
111
server/test/fixtures/asset.stub.ts
vendored
|
@ -1,12 +1,33 @@
|
|||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { ExifEntity } from 'src/entities/exif.entity';
|
||||
import { StackEntity } from 'src/entities/stack.entity';
|
||||
import { AssetType } from 'src/enum';
|
||||
import { AssetFileType, AssetType } from 'src/enum';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { fileStub } from 'test/fixtures/file.stub';
|
||||
import { libraryStub } from 'test/fixtures/library.stub';
|
||||
import { userStub } from 'test/fixtures/user.stub';
|
||||
|
||||
const previewFile: AssetFileEntity = {
|
||||
id: 'file-1',
|
||||
assetId: 'asset-id',
|
||||
type: AssetFileType.PREVIEW,
|
||||
path: '/uploads/user-id/thumbs/path.jpg',
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
};
|
||||
|
||||
const thumbnailFile: AssetFileEntity = {
|
||||
id: 'file-2',
|
||||
assetId: 'asset-id',
|
||||
type: AssetFileType.THUMBNAIL,
|
||||
path: '/uploads/user-id/webp/path.ext',
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
};
|
||||
|
||||
const files: AssetFileEntity[] = [previewFile, thumbnailFile];
|
||||
|
||||
export const stackStub = (stackId: string, assets: AssetEntity[]): StackEntity => {
|
||||
return {
|
||||
id: stackId,
|
||||
|
@ -29,10 +50,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: 'upload/library/IMG_123.jpg',
|
||||
previewPath: null,
|
||||
files: [thumbnailFile],
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -63,10 +83,10 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: 'upload/library/IMG_456.jpg',
|
||||
previewPath: '/uploads/user-id/thumbs/path.ext',
|
||||
|
||||
files: [previewFile],
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: null,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -101,10 +121,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.ext',
|
||||
previewPath: '/uploads/user-id/thumbs/path.ext',
|
||||
files,
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
thumbhash: null,
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -136,10 +155,9 @@ export const assetStub = {
|
|||
ownerId: 'admin-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.jpg',
|
||||
previewPath: '/uploads/admin-id/thumbs/path.jpg',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
files,
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/admin-id/webp/path.ext',
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -181,10 +199,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.jpg',
|
||||
previewPath: '/uploads/user-id/thumbs/path.jpg',
|
||||
files,
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -221,10 +238,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.jpg',
|
||||
previewPath: '/uploads/user-id/thumbs/path.jpg',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
files,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -261,10 +277,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.jpg',
|
||||
previewPath: '/uploads/user-id/thumbs/path.jpg',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
files,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -301,10 +316,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/data/user1/photo.jpg',
|
||||
previewPath: '/uploads/user-id/thumbs/path.jpg',
|
||||
checksum: Buffer.from('path hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
files,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -341,10 +355,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.jpg',
|
||||
previewPath: '/uploads/user-id/thumbs/path.jpg',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
files,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -379,10 +392,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/data/user1/photo.jpg',
|
||||
previewPath: '/uploads/user-id/thumbs/path.jpg',
|
||||
checksum: Buffer.from('path hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
files,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -419,10 +431,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.ext',
|
||||
previewPath: '/uploads/user-id/thumbs/path.ext',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
files,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -457,10 +468,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.ext',
|
||||
previewPath: '/uploads/user-id/thumbs/path.ext',
|
||||
files,
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2015-02-23T05:06:29.716Z'),
|
||||
|
@ -496,10 +506,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.ext',
|
||||
previewPath: '/uploads/user-id/thumbs/path.ext',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.VIDEO,
|
||||
thumbnailPath: null,
|
||||
files: [previewFile],
|
||||
thumbhash: null,
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -548,8 +557,22 @@ export const assetStub = {
|
|||
isVisible: false,
|
||||
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
previewPath: '/uploads/user-id/thumbs/path.ext',
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
files: [
|
||||
{
|
||||
assetId: 'asset-id',
|
||||
type: AssetFileType.PREVIEW,
|
||||
path: '/uploads/user-id/thumbs/path.ext',
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
},
|
||||
{
|
||||
assetId: 'asset-id',
|
||||
type: AssetFileType.THUMBNAIL,
|
||||
path: '/uploads/user-id/webp/path.ext',
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
},
|
||||
],
|
||||
exifInfo: {
|
||||
fileSizeInByte: 100_000,
|
||||
timeZone: `America/New_York`,
|
||||
|
@ -612,10 +635,9 @@ export const assetStub = {
|
|||
deviceId: 'device-id',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
originalPath: '/original/path.ext',
|
||||
previewPath: '/uploads/user-id/thumbs/path.ext',
|
||||
sidecarPath: null,
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: null,
|
||||
files: [previewFile],
|
||||
thumbhash: null,
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-22T05:06:29.716Z'),
|
||||
|
@ -653,11 +675,10 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.ext',
|
||||
previewPath: '/uploads/user-id/thumbs/path.ext',
|
||||
thumbhash: null,
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: null,
|
||||
files: [previewFile],
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -687,11 +708,10 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.ext',
|
||||
previewPath: '/uploads/user-id/thumbs/path.ext',
|
||||
thumbhash: null,
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: null,
|
||||
files: [previewFile],
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -722,11 +742,10 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.ext',
|
||||
previewPath: '/uploads/user-id/thumbs/path.ext',
|
||||
thumbhash: null,
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: null,
|
||||
files: [previewFile],
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -758,10 +777,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.ext',
|
||||
previewPath: '/uploads/user-id/thumbs/path.ext',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.VIDEO,
|
||||
thumbnailPath: null,
|
||||
files: [previewFile],
|
||||
thumbhash: null,
|
||||
encodedVideoPath: '/encoded/video/path.mp4',
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -794,10 +812,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/data/user1/photo.jpg',
|
||||
previewPath: '/uploads/user-id/thumbs/path.jpg',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
files,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -833,10 +850,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/data/user1/photo.jpg',
|
||||
previewPath: '/uploads/user-id/thumbs/path.jpg',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
files,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -872,10 +888,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.dng',
|
||||
previewPath: '/uploads/user-id/thumbs/path.jpg',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
files,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -911,10 +926,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.jpg',
|
||||
previewPath: '/uploads/user-id/thumbs/path.jpg',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
files,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
@ -952,10 +966,9 @@ export const assetStub = {
|
|||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.jpg',
|
||||
previewPath: '/uploads/user-id/thumbs/path.jpg',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
thumbnailPath: '/uploads/user-id/webp/path.ext',
|
||||
files,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
|
|
3
server/test/fixtures/shared-link.stub.ts
vendored
3
server/test/fixtures/shared-link.stub.ts
vendored
|
@ -196,7 +196,6 @@ export const sharedLinkStub = {
|
|||
deviceId: 'device_id_1',
|
||||
type: AssetType.VIDEO,
|
||||
originalPath: 'fake_path/jpeg',
|
||||
previewPath: '',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
fileModifiedAt: today,
|
||||
fileCreatedAt: today,
|
||||
|
@ -213,7 +212,7 @@ export const sharedLinkStub = {
|
|||
objects: ['a', 'b', 'c'],
|
||||
asset: null as any,
|
||||
},
|
||||
thumbnailPath: '',
|
||||
files: [],
|
||||
thumbhash: null,
|
||||
encodedVideoPath: '',
|
||||
duration: null,
|
||||
|
|
|
@ -42,5 +42,6 @@ export const newAssetRepositoryMock = (): Mocked<IAssetRepository> => {
|
|||
getAllForUserFullSync: vitest.fn(),
|
||||
getChangedDeltaSync: vitest.fn(),
|
||||
getDuplicates: vitest.fn(),
|
||||
upsertFile: vitest.fn(),
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue