mirror of
https://github.com/samsonjs/immich.git
synced 2026-04-27 15:07:45 +00:00
feat(server): add /search/statistics resource (#18885)
This commit is contained in:
parent
ecb16d9907
commit
fb4be6e231
14 changed files with 299 additions and 16 deletions
BIN
mobile/openapi/README.md
generated
BIN
mobile/openapi/README.md
generated
Binary file not shown.
BIN
mobile/openapi/lib/api.dart
generated
BIN
mobile/openapi/lib/api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/search_api.dart
generated
BIN
mobile/openapi/lib/api/search_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api_client.dart
generated
BIN
mobile/openapi/lib/api_client.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/model/search_statistics_response_dto.dart
generated
Normal file
BIN
mobile/openapi/lib/model/search_statistics_response_dto.dart
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/lib/model/statistics_search_dto.dart
generated
Normal file
BIN
mobile/openapi/lib/model/statistics_search_dto.dart
generated
Normal file
Binary file not shown.
|
|
@ -5158,6 +5158,48 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/search/statistics": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "searchAssetStatistics",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/StatisticsSearchDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SearchStatisticsResponseDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cookie": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"api_key": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Search"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/search/suggestions": {
|
"/search/suggestions": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getSearchSuggestions",
|
"operationId": "getSearchSuggestions",
|
||||||
|
|
@ -12069,6 +12111,17 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"SearchStatisticsResponseDto": {
|
||||||
|
"properties": {
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"total"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"SearchSuggestionType": {
|
"SearchSuggestionType": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"country",
|
"country",
|
||||||
|
|
@ -12974,6 +13027,125 @@
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"StatisticsSearchDto": {
|
||||||
|
"properties": {
|
||||||
|
"city": {
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"country": {
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdAfter": {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdBefore": {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"deviceId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isEncoded": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"isFavorite": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"isMotion": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"isNotInAlbum": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"isOffline": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"lensModel": {
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"libraryId": {
|
||||||
|
"format": "uuid",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"make": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"personIds": {
|
||||||
|
"items": {
|
||||||
|
"format": "uuid",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"maximum": 5,
|
||||||
|
"minimum": -1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tagIds": {
|
||||||
|
"items": {
|
||||||
|
"format": "uuid",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"takenAfter": {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"takenBefore": {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"trashedAfter": {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"trashedBefore": {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/AssetTypeEnum"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"updatedAfter": {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedBefore": {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/AssetVisibility"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"SyncAckDeleteDto": {
|
"SyncAckDeleteDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"types": {
|
"types": {
|
||||||
|
|
|
||||||
|
|
@ -995,6 +995,38 @@ export type SmartSearchDto = {
|
||||||
withDeleted?: boolean;
|
withDeleted?: boolean;
|
||||||
withExif?: boolean;
|
withExif?: boolean;
|
||||||
};
|
};
|
||||||
|
export type StatisticsSearchDto = {
|
||||||
|
city?: string | null;
|
||||||
|
country?: string | null;
|
||||||
|
createdAfter?: string;
|
||||||
|
createdBefore?: string;
|
||||||
|
description?: string;
|
||||||
|
deviceId?: string;
|
||||||
|
isEncoded?: boolean;
|
||||||
|
isFavorite?: boolean;
|
||||||
|
isMotion?: boolean;
|
||||||
|
isNotInAlbum?: boolean;
|
||||||
|
isOffline?: boolean;
|
||||||
|
lensModel?: string | null;
|
||||||
|
libraryId?: string | null;
|
||||||
|
make?: string;
|
||||||
|
model?: string | null;
|
||||||
|
personIds?: string[];
|
||||||
|
rating?: number;
|
||||||
|
state?: string | null;
|
||||||
|
tagIds?: string[];
|
||||||
|
takenAfter?: string;
|
||||||
|
takenBefore?: string;
|
||||||
|
trashedAfter?: string;
|
||||||
|
trashedBefore?: string;
|
||||||
|
"type"?: AssetTypeEnum;
|
||||||
|
updatedAfter?: string;
|
||||||
|
updatedBefore?: string;
|
||||||
|
visibility?: AssetVisibility;
|
||||||
|
};
|
||||||
|
export type SearchStatisticsResponseDto = {
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
export type ServerAboutResponseDto = {
|
export type ServerAboutResponseDto = {
|
||||||
build?: string;
|
build?: string;
|
||||||
buildImage?: string;
|
buildImage?: string;
|
||||||
|
|
@ -2882,6 +2914,18 @@ export function searchSmart({ smartSearchDto }: {
|
||||||
body: smartSearchDto
|
body: smartSearchDto
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
export function searchAssetStatistics({ statisticsSearchDto }: {
|
||||||
|
statisticsSearchDto: StatisticsSearchDto;
|
||||||
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
|
status: 200;
|
||||||
|
data: SearchStatisticsResponseDto;
|
||||||
|
}>("/search/statistics", oazapfts.json({
|
||||||
|
...opts,
|
||||||
|
method: "POST",
|
||||||
|
body: statisticsSearchDto
|
||||||
|
})));
|
||||||
|
}
|
||||||
export function getSearchSuggestions({ country, includeNull, make, model, state, $type }: {
|
export function getSearchSuggestions({ country, includeNull, make, model, state, $type }: {
|
||||||
country?: string;
|
country?: string;
|
||||||
includeNull?: boolean;
|
includeNull?: boolean;
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,10 @@ import {
|
||||||
SearchPeopleDto,
|
SearchPeopleDto,
|
||||||
SearchPlacesDto,
|
SearchPlacesDto,
|
||||||
SearchResponseDto,
|
SearchResponseDto,
|
||||||
|
SearchStatisticsResponseDto,
|
||||||
SearchSuggestionRequestDto,
|
SearchSuggestionRequestDto,
|
||||||
SmartSearchDto,
|
SmartSearchDto,
|
||||||
|
StatisticsSearchDto,
|
||||||
} from 'src/dtos/search.dto';
|
} from 'src/dtos/search.dto';
|
||||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { SearchService } from 'src/services/search.service';
|
import { SearchService } from 'src/services/search.service';
|
||||||
|
|
@ -29,6 +31,13 @@ export class SearchController {
|
||||||
return this.service.searchMetadata(auth, dto);
|
return this.service.searchMetadata(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('statistics')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Authenticated()
|
||||||
|
searchAssetStatistics(@Auth() auth: AuthDto, @Body() dto: StatisticsSearchDto): Promise<SearchStatisticsResponseDto> {
|
||||||
|
return this.service.searchStatistics(auth, dto);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('random')
|
@Post('random')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Authenticated()
|
@Authenticated()
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,6 @@ class BaseSearchDto {
|
||||||
@ValidateAssetVisibility({ optional: true })
|
@ValidateAssetVisibility({ optional: true })
|
||||||
visibility?: AssetVisibility;
|
visibility?: AssetVisibility;
|
||||||
|
|
||||||
@ValidateBoolean({ optional: true })
|
|
||||||
withDeleted?: boolean;
|
|
||||||
|
|
||||||
@ValidateBoolean({ optional: true })
|
|
||||||
withExif?: boolean;
|
|
||||||
|
|
||||||
@ValidateDate({ optional: true })
|
@ValidateDate({ optional: true })
|
||||||
createdBefore?: Date;
|
createdBefore?: Date;
|
||||||
|
|
||||||
|
|
@ -92,13 +86,6 @@ class BaseSearchDto {
|
||||||
@Optional({ nullable: true, emptyToNull: true })
|
@Optional({ nullable: true, emptyToNull: true })
|
||||||
lensModel?: string | null;
|
lensModel?: string | null;
|
||||||
|
|
||||||
@IsInt()
|
|
||||||
@Min(1)
|
|
||||||
@Max(1000)
|
|
||||||
@Type(() => Number)
|
|
||||||
@Optional()
|
|
||||||
size?: number;
|
|
||||||
|
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
isNotInAlbum?: boolean;
|
isNotInAlbum?: boolean;
|
||||||
|
|
||||||
|
|
@ -115,7 +102,22 @@ class BaseSearchDto {
|
||||||
rating?: number;
|
rating?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RandomSearchDto extends BaseSearchDto {
|
class BaseSearchWithResultsDto extends BaseSearchDto {
|
||||||
|
@ValidateBoolean({ optional: true })
|
||||||
|
withDeleted?: boolean;
|
||||||
|
|
||||||
|
@ValidateBoolean({ optional: true })
|
||||||
|
withExif?: boolean;
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
@Max(1000)
|
||||||
|
@Type(() => Number)
|
||||||
|
@Optional()
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RandomSearchDto extends BaseSearchWithResultsDto {
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
withStacked?: boolean;
|
withStacked?: boolean;
|
||||||
|
|
||||||
|
|
@ -179,7 +181,14 @@ export class MetadataSearchDto extends RandomSearchDto {
|
||||||
page?: number;
|
page?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SmartSearchDto extends BaseSearchDto {
|
export class StatisticsSearchDto extends BaseSearchDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@Optional()
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SmartSearchDto extends BaseSearchWithResultsDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
query!: string;
|
query!: string;
|
||||||
|
|
@ -299,6 +308,11 @@ export class SearchResponseDto {
|
||||||
assets!: SearchAssetResponseDto;
|
assets!: SearchAssetResponseDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SearchStatisticsResponseDto {
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
|
total!: number;
|
||||||
|
}
|
||||||
|
|
||||||
class SearchExploreItem {
|
class SearchExploreItem {
|
||||||
value!: string;
|
value!: string;
|
||||||
data!: AssetResponseDto;
|
data!: AssetResponseDto;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,20 @@ limit
|
||||||
offset
|
offset
|
||||||
$7
|
$7
|
||||||
|
|
||||||
|
-- SearchRepository.searchStatistics
|
||||||
|
select
|
||||||
|
count(*) as "total"
|
||||||
|
from
|
||||||
|
"assets"
|
||||||
|
inner join "exif" on "assets"."id" = "exif"."assetId"
|
||||||
|
where
|
||||||
|
"assets"."visibility" = $1
|
||||||
|
and "assets"."fileCreatedAt" >= $2
|
||||||
|
and "exif"."lensModel" = $3
|
||||||
|
and "assets"."ownerId" = any ($4::uuid[])
|
||||||
|
and "assets"."isFavorite" = $5
|
||||||
|
and "assets"."deletedAt" is null
|
||||||
|
|
||||||
-- SearchRepository.searchRandom
|
-- SearchRepository.searchRandom
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,7 @@ export class SearchRepository {
|
||||||
async searchMetadata(pagination: SearchPaginationOptions, options: AssetSearchOptions) {
|
async searchMetadata(pagination: SearchPaginationOptions, options: AssetSearchOptions) {
|
||||||
const orderDirection = (options.orderDirection?.toLowerCase() || 'desc') as OrderByDirection;
|
const orderDirection = (options.orderDirection?.toLowerCase() || 'desc') as OrderByDirection;
|
||||||
const items = await searchAssetBuilder(this.db, options)
|
const items = await searchAssetBuilder(this.db, options)
|
||||||
|
.selectAll('assets')
|
||||||
.orderBy('assets.fileCreatedAt', orderDirection)
|
.orderBy('assets.fileCreatedAt', orderDirection)
|
||||||
.limit(pagination.size + 1)
|
.limit(pagination.size + 1)
|
||||||
.offset((pagination.page - 1) * pagination.size)
|
.offset((pagination.page - 1) * pagination.size)
|
||||||
|
|
@ -193,6 +194,22 @@ export class SearchRepository {
|
||||||
return paginationHelper(items, pagination.size);
|
return paginationHelper(items, pagination.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
takenAfter: DummyValue.DATE,
|
||||||
|
lensModel: DummyValue.STRING,
|
||||||
|
isFavorite: true,
|
||||||
|
userIds: [DummyValue.UUID],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
searchStatistics(options: AssetSearchOptions) {
|
||||||
|
return searchAssetBuilder(this.db, options)
|
||||||
|
.select((qb) => qb.fn.countAll<number>().as('total'))
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
}
|
||||||
|
|
||||||
@GenerateSql({
|
@GenerateSql({
|
||||||
params: [
|
params: [
|
||||||
100,
|
100,
|
||||||
|
|
@ -209,10 +226,12 @@ export class SearchRepository {
|
||||||
const uuid = randomUUID();
|
const uuid = randomUUID();
|
||||||
const builder = searchAssetBuilder(this.db, options);
|
const builder = searchAssetBuilder(this.db, options);
|
||||||
const lessThan = builder
|
const lessThan = builder
|
||||||
|
.selectAll('assets')
|
||||||
.where('assets.id', '<', uuid)
|
.where('assets.id', '<', uuid)
|
||||||
.orderBy(sql`random()`)
|
.orderBy(sql`random()`)
|
||||||
.limit(size);
|
.limit(size);
|
||||||
const greaterThan = builder
|
const greaterThan = builder
|
||||||
|
.selectAll('assets')
|
||||||
.where('assets.id', '>', uuid)
|
.where('assets.id', '>', uuid)
|
||||||
.orderBy(sql`random()`)
|
.orderBy(sql`random()`)
|
||||||
.limit(size);
|
.limit(size);
|
||||||
|
|
@ -241,6 +260,7 @@ export class SearchRepository {
|
||||||
return this.db.transaction().execute(async (trx) => {
|
return this.db.transaction().execute(async (trx) => {
|
||||||
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.CLIP])}`.execute(trx);
|
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.CLIP])}`.execute(trx);
|
||||||
const items = await searchAssetBuilder(trx, options)
|
const items = await searchAssetBuilder(trx, options)
|
||||||
|
.selectAll('assets')
|
||||||
.innerJoin('smart_search', 'assets.id', 'smart_search.assetId')
|
.innerJoin('smart_search', 'assets.id', 'smart_search.assetId')
|
||||||
.orderBy(sql`smart_search.embedding <=> ${options.embedding}`)
|
.orderBy(sql`smart_search.embedding <=> ${options.embedding}`)
|
||||||
.limit(pagination.size + 1)
|
.limit(pagination.size + 1)
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,11 @@ import {
|
||||||
SearchPeopleDto,
|
SearchPeopleDto,
|
||||||
SearchPlacesDto,
|
SearchPlacesDto,
|
||||||
SearchResponseDto,
|
SearchResponseDto,
|
||||||
|
SearchStatisticsResponseDto,
|
||||||
SearchSuggestionRequestDto,
|
SearchSuggestionRequestDto,
|
||||||
SearchSuggestionType,
|
SearchSuggestionType,
|
||||||
SmartSearchDto,
|
SmartSearchDto,
|
||||||
|
StatisticsSearchDto,
|
||||||
} from 'src/dtos/search.dto';
|
} from 'src/dtos/search.dto';
|
||||||
import { AssetOrder, AssetVisibility } from 'src/enum';
|
import { AssetOrder, AssetVisibility } from 'src/enum';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
|
|
@ -67,6 +69,15 @@ export class SearchService extends BaseService {
|
||||||
return this.mapResponse(items, hasNextPage ? (page + 1).toString() : null, { auth });
|
return this.mapResponse(items, hasNextPage ? (page + 1).toString() : null, { auth });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async searchStatistics(auth: AuthDto, dto: StatisticsSearchDto): Promise<SearchStatisticsResponseDto> {
|
||||||
|
const userIds = await this.getUserIdsToSearch(auth);
|
||||||
|
|
||||||
|
return await this.searchRepository.searchStatistics({
|
||||||
|
...dto,
|
||||||
|
userIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async searchRandom(auth: AuthDto, dto: RandomSearchDto): Promise<AssetResponseDto[]> {
|
async searchRandom(auth: AuthDto, dto: RandomSearchDto): Promise<AssetResponseDto[]> {
|
||||||
if (dto.visibility === AssetVisibility.LOCKED) {
|
if (dto.visibility === AssetVisibility.LOCKED) {
|
||||||
requireElevatedPermission(auth);
|
requireElevatedPermission(auth);
|
||||||
|
|
|
||||||
|
|
@ -291,7 +291,6 @@ export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuild
|
||||||
return kysely
|
return kysely
|
||||||
.withPlugin(joinDeduplicationPlugin)
|
.withPlugin(joinDeduplicationPlugin)
|
||||||
.selectFrom('assets')
|
.selectFrom('assets')
|
||||||
.selectAll('assets')
|
|
||||||
.where('assets.visibility', '=', visibility)
|
.where('assets.visibility', '=', visibility)
|
||||||
.$if(!!options.tagIds && options.tagIds.length > 0, (qb) => hasTags(qb, options.tagIds!))
|
.$if(!!options.tagIds && options.tagIds.length > 0, (qb) => hasTags(qb, options.tagIds!))
|
||||||
.$if(!!options.personIds && options.personIds.length > 0, (qb) => hasPeople(qb, options.personIds!))
|
.$if(!!options.personIds && options.personIds.length > 0, (qb) => hasPeople(qb, options.personIds!))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue