mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
feat: find large files utility (#18040)
feat: large asset utility Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
parent
7d759edfcc
commit
ae1d60e259
17 changed files with 964 additions and 4 deletions
|
|
@ -4,6 +4,7 @@ import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
|||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { PersonResponseDto } from 'src/dtos/person.dto';
|
||||
import {
|
||||
LargeAssetSearchDto,
|
||||
MetadataSearchDto,
|
||||
PlacesResponseDto,
|
||||
RandomSearchDto,
|
||||
|
|
@ -46,6 +47,13 @@ export class SearchController {
|
|||
return this.service.searchRandom(auth, dto);
|
||||
}
|
||||
|
||||
@Post('large-assets')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Authenticated({ permission: Permission.AssetRead })
|
||||
searchLargeAssets(@Auth() auth: AuthDto, @Query() dto: LargeAssetSearchDto): Promise<AssetResponseDto[]> {
|
||||
return this.service.searchLargeAssets(auth, dto);
|
||||
}
|
||||
|
||||
@Post('smart')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Authenticated({ permission: Permission.AssetRead })
|
||||
|
|
|
|||
|
|
@ -126,6 +126,15 @@ export class RandomSearchDto extends BaseSearchWithResultsDto {
|
|||
withPeople?: boolean;
|
||||
}
|
||||
|
||||
export class LargeAssetSearchDto extends BaseSearchWithResultsDto {
|
||||
@Optional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Type(() => Number)
|
||||
@ApiProperty({ type: 'integer' })
|
||||
minFileSize?: number;
|
||||
}
|
||||
|
||||
export class MetadataSearchDto extends RandomSearchDto {
|
||||
@ValidateUUID({ optional: true })
|
||||
id?: string;
|
||||
|
|
|
|||
|
|
@ -77,6 +77,27 @@ union all
|
|||
limit
|
||||
$15
|
||||
|
||||
-- SearchRepository.searchLargeAssets
|
||||
select
|
||||
"asset".*,
|
||||
to_json("asset_exif") as "exifInfo"
|
||||
from
|
||||
"asset"
|
||||
inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
|
||||
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
|
||||
where
|
||||
"asset"."visibility" = $1
|
||||
and "asset"."fileCreatedAt" >= $2
|
||||
and "asset_exif"."lensModel" = $3
|
||||
and "asset"."ownerId" = any ($4::uuid[])
|
||||
and "asset"."isFavorite" = $5
|
||||
and "asset"."deletedAt" is null
|
||||
and "asset_exif"."fileSizeInByte" > $6
|
||||
order by
|
||||
"asset_exif"."fileSizeInByte" desc
|
||||
limit
|
||||
$7
|
||||
|
||||
-- SearchRepository.searchSmart
|
||||
begin
|
||||
set
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { AssetStatus, AssetType, AssetVisibility, VectorIndex } from 'src/enum';
|
|||
import { probes } from 'src/repositories/database.repository';
|
||||
import { DB } from 'src/schema';
|
||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||
import { anyUuid, searchAssetBuilder } from 'src/utils/database';
|
||||
import { anyUuid, searchAssetBuilder, withExif } from 'src/utils/database';
|
||||
import { paginationHelper } from 'src/utils/pagination';
|
||||
import { isValidInteger } from 'src/validation';
|
||||
|
||||
|
|
@ -129,6 +129,8 @@ export type SmartSearchOptions = SearchDateOptions &
|
|||
SearchPeopleOptions &
|
||||
SearchTagOptions;
|
||||
|
||||
export type LargeAssetSearchOptions = AssetSearchOptions & { minFileSize?: number };
|
||||
|
||||
export interface FaceEmbeddingSearch extends SearchEmbeddingOptions {
|
||||
hasPerson?: boolean;
|
||||
numResults: number;
|
||||
|
|
@ -237,6 +239,29 @@ export class SearchRepository {
|
|||
return rows;
|
||||
}
|
||||
|
||||
@GenerateSql({
|
||||
params: [
|
||||
100,
|
||||
{
|
||||
takenAfter: DummyValue.DATE,
|
||||
lensModel: DummyValue.STRING,
|
||||
withStacked: true,
|
||||
isFavorite: true,
|
||||
userIds: [DummyValue.UUID],
|
||||
},
|
||||
],
|
||||
})
|
||||
searchLargeAssets(size: number, options: LargeAssetSearchOptions) {
|
||||
const orderDirection = (options.orderDirection?.toLowerCase() || 'desc') as OrderByDirection;
|
||||
return searchAssetBuilder(this.db, options)
|
||||
.selectAll('asset')
|
||||
.$call(withExif)
|
||||
.where('asset_exif.fileSizeInByte', '>', options.minFileSize || 0)
|
||||
.orderBy('asset_exif.fileSizeInByte', orderDirection)
|
||||
.limit(size)
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({
|
||||
params: [
|
||||
{ page: 1, size: 200 },
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { AssetMapOptions, AssetResponseDto, MapAsset, mapAsset } from 'src/dtos/
|
|||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { mapPerson, PersonResponseDto } from 'src/dtos/person.dto';
|
||||
import {
|
||||
LargeAssetSearchDto,
|
||||
mapPlaces,
|
||||
MetadataSearchDto,
|
||||
PlacesResponseDto,
|
||||
|
|
@ -91,6 +92,16 @@ export class SearchService extends BaseService {
|
|||
return items.map((item) => mapAsset(item, { auth }));
|
||||
}
|
||||
|
||||
async searchLargeAssets(auth: AuthDto, dto: LargeAssetSearchDto): Promise<AssetResponseDto[]> {
|
||||
if (dto.visibility === AssetVisibility.Locked) {
|
||||
requireElevatedPermission(auth);
|
||||
}
|
||||
|
||||
const userIds = await this.getUserIdsToSearch(auth);
|
||||
const items = await this.searchRepository.searchLargeAssets(dto.size || 250, { ...dto, userIds });
|
||||
return items.map((item) => mapAsset(item, { auth }));
|
||||
}
|
||||
|
||||
async searchSmart(auth: AuthDto, dto: SmartSearchDto): Promise<SearchResponseDto> {
|
||||
if (dto.visibility === AssetVisibility.Locked) {
|
||||
requireElevatedPermission(auth);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue