feat: find large files utility (#18040)

feat: large asset utility

Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
Alwin Lohrie 2025-07-29 00:48:39 +02:00 committed by GitHub
parent 7d759edfcc
commit ae1d60e259
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 964 additions and 4 deletions

View file

@ -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 })

View file

@ -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;

View file

@ -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

View file

@ -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 },

View file

@ -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);