mirror of
https://github.com/immich-app/immich.git
synced 2026-03-04 09:57:33 +03:00
fix: empty and restore over 1,000 items (#12751)
This commit is contained in:
@@ -6,7 +6,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 { SmartInfoEntity } from 'src/entities/smart-info.entity';
|
||||
import { AssetFileType, AssetOrder, AssetType } from 'src/enum';
|
||||
import { AssetFileType, AssetOrder, AssetStatus, AssetType } from 'src/enum';
|
||||
import {
|
||||
AssetBuilderOptions,
|
||||
AssetCreate,
|
||||
@@ -295,16 +295,6 @@ export class AssetRepository implements IAssetRepository {
|
||||
.execute();
|
||||
}
|
||||
|
||||
@Chunked()
|
||||
async softDeleteAll(ids: string[]): Promise<void> {
|
||||
await this.repository.softDelete({ id: In(ids) });
|
||||
}
|
||||
|
||||
@Chunked()
|
||||
async restoreAll(ids: string[]): Promise<void> {
|
||||
await this.repository.restore({ id: In(ids) });
|
||||
}
|
||||
|
||||
async update(asset: AssetUpdateOptions): Promise<void> {
|
||||
await this.repository.update(asset.id, asset);
|
||||
}
|
||||
@@ -597,7 +587,10 @@ export class AssetRepository implements IAssetRepository {
|
||||
}
|
||||
|
||||
if (isTrashed !== undefined) {
|
||||
builder.withDeleted().andWhere(`asset.deletedAt is not null`);
|
||||
builder
|
||||
.withDeleted()
|
||||
.andWhere(`asset.deletedAt is not null`)
|
||||
.andWhere('asset.status = :status', { status: AssetStatus.TRASHED });
|
||||
}
|
||||
|
||||
const items = await builder.getRawMany();
|
||||
@@ -755,6 +748,10 @@ export class AssetRepository implements IAssetRepository {
|
||||
|
||||
if (options.isTrashed !== undefined) {
|
||||
builder.andWhere(`asset.deletedAt ${options.isTrashed ? 'IS NOT NULL' : 'IS NULL'}`).withDeleted();
|
||||
|
||||
if (options.isTrashed) {
|
||||
builder.andWhere('asset.status = :status', { status: AssetStatus.TRASHED });
|
||||
}
|
||||
}
|
||||
|
||||
if (options.isDuplicate !== undefined) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import { IStackRepository } from 'src/interfaces/stack.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { ITagRepository } from 'src/interfaces/tag.interface';
|
||||
import { ITrashRepository } from 'src/interfaces/trash.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { IViewRepository } from 'src/interfaces/view.interface';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
@@ -62,6 +63,7 @@ import { StackRepository } from 'src/repositories/stack.repository';
|
||||
import { StorageRepository } from 'src/repositories/storage.repository';
|
||||
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
|
||||
import { TagRepository } from 'src/repositories/tag.repository';
|
||||
import { TrashRepository } from 'src/repositories/trash.repository';
|
||||
import { UserRepository } from 'src/repositories/user.repository';
|
||||
import { ViewRepository } from 'src/repositories/view-repository';
|
||||
|
||||
@@ -97,6 +99,7 @@ export const repositories = [
|
||||
{ provide: IStorageRepository, useClass: StorageRepository },
|
||||
{ provide: ISystemMetadataRepository, useClass: SystemMetadataRepository },
|
||||
{ provide: ITagRepository, useClass: TagRepository },
|
||||
{ provide: ITrashRepository, useClass: TrashRepository },
|
||||
{ provide: IUserRepository, useClass: UserRepository },
|
||||
{ provide: IViewRepository, useClass: ViewRepository },
|
||||
];
|
||||
|
||||
@@ -95,6 +95,9 @@ export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
|
||||
|
||||
// Version check
|
||||
[JobName.VERSION_CHECK]: QueueName.BACKGROUND_TASK,
|
||||
|
||||
// Trash
|
||||
[JobName.QUEUE_TRASH_EMPTY]: QueueName.BACKGROUND_TASK,
|
||||
};
|
||||
|
||||
@Instrumentation()
|
||||
|
||||
49
server/src/repositories/trash.repository.ts
Normal file
49
server/src/repositories/trash.repository.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { AssetStatus } from 'src/enum';
|
||||
import { ITrashRepository } from 'src/interfaces/trash.interface';
|
||||
import { Paginated, paginatedBuilder, PaginationOptions } from 'src/utils/pagination';
|
||||
import { In, IsNull, Not, Repository } from 'typeorm';
|
||||
|
||||
export class TrashRepository implements ITrashRepository {
|
||||
constructor(@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>) {}
|
||||
|
||||
async getDeletedIds(pagination: PaginationOptions): Paginated<string> {
|
||||
const { hasNextPage, items } = await paginatedBuilder(
|
||||
this.assetRepository
|
||||
.createQueryBuilder('asset')
|
||||
.select('asset.id')
|
||||
.where({ status: AssetStatus.DELETED })
|
||||
.withDeleted(),
|
||||
pagination,
|
||||
);
|
||||
|
||||
return {
|
||||
hasNextPage,
|
||||
items: items.map((asset) => asset.id),
|
||||
};
|
||||
}
|
||||
|
||||
async restore(userId: string): Promise<number> {
|
||||
const result = await this.assetRepository.update(
|
||||
{ ownerId: userId, deletedAt: Not(IsNull()) },
|
||||
{ status: AssetStatus.ACTIVE, deletedAt: null },
|
||||
);
|
||||
|
||||
return result.affected || 0;
|
||||
}
|
||||
|
||||
async empty(userId: string): Promise<number> {
|
||||
const result = await this.assetRepository.update(
|
||||
{ ownerId: userId, deletedAt: Not(IsNull()), status: AssetStatus.TRASHED },
|
||||
{ status: AssetStatus.DELETED },
|
||||
);
|
||||
|
||||
return result.affected || 0;
|
||||
}
|
||||
|
||||
async restoreAll(ids: string[]): Promise<number> {
|
||||
const result = await this.assetRepository.update({ id: In(ids) }, { status: AssetStatus.ACTIVE, deletedAt: null });
|
||||
return result.affected ?? 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user