formatting
This commit is contained in:
parent
436f99c747
commit
1338c5fd59
19 changed files with 75 additions and 217 deletions
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -213,7 +213,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: tensorchord/pgvecto-rs:pg14-v0.1.10
|
image: tensorchord/pgvecto-rs:pg14-v0.1.11
|
||||||
env:
|
env:
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
|
|
24
mobile/openapi/doc/SearchApi.md
generated
24
mobile/openapi/doc/SearchApi.md
generated
|
@ -66,7 +66,7 @@ This endpoint does not need any parameter.
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||||
|
|
||||||
# **search**
|
# **search**
|
||||||
> SearchResponseDto search(q, query, clip, type, isFavorite, isArchived, exifInfoPeriodCity, exifInfoPeriodState, exifInfoPeriodCountry, exifInfoPeriodMake, exifInfoPeriodModel, exifInfoPeriodProjectionType, smartInfoPeriodObjects, smartInfoPeriodTags, recent, motion)
|
> SearchResponseDto search(q, query, clip, type, recent, motion)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,21 +93,11 @@ final q = q_example; // String |
|
||||||
final query = query_example; // String |
|
final query = query_example; // String |
|
||||||
final clip = true; // bool |
|
final clip = true; // bool |
|
||||||
final type = type_example; // String |
|
final type = type_example; // String |
|
||||||
final isFavorite = true; // bool |
|
|
||||||
final isArchived = true; // bool |
|
|
||||||
final exifInfoPeriodCity = exifInfoPeriodCity_example; // String |
|
|
||||||
final exifInfoPeriodState = exifInfoPeriodState_example; // String |
|
|
||||||
final exifInfoPeriodCountry = exifInfoPeriodCountry_example; // String |
|
|
||||||
final exifInfoPeriodMake = exifInfoPeriodMake_example; // String |
|
|
||||||
final exifInfoPeriodModel = exifInfoPeriodModel_example; // String |
|
|
||||||
final exifInfoPeriodProjectionType = exifInfoPeriodProjectionType_example; // String |
|
|
||||||
final smartInfoPeriodObjects = []; // List<String> |
|
|
||||||
final smartInfoPeriodTags = []; // List<String> |
|
|
||||||
final recent = true; // bool |
|
final recent = true; // bool |
|
||||||
final motion = true; // bool |
|
final motion = true; // bool |
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = api_instance.search(q, query, clip, type, isFavorite, isArchived, exifInfoPeriodCity, exifInfoPeriodState, exifInfoPeriodCountry, exifInfoPeriodMake, exifInfoPeriodModel, exifInfoPeriodProjectionType, smartInfoPeriodObjects, smartInfoPeriodTags, recent, motion);
|
final result = api_instance.search(q, query, clip, type, recent, motion);
|
||||||
print(result);
|
print(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Exception when calling SearchApi->search: $e\n');
|
print('Exception when calling SearchApi->search: $e\n');
|
||||||
|
@ -122,16 +112,6 @@ Name | Type | Description | Notes
|
||||||
**query** | **String**| | [optional]
|
**query** | **String**| | [optional]
|
||||||
**clip** | **bool**| | [optional]
|
**clip** | **bool**| | [optional]
|
||||||
**type** | **String**| | [optional]
|
**type** | **String**| | [optional]
|
||||||
**isFavorite** | **bool**| | [optional]
|
|
||||||
**isArchived** | **bool**| | [optional]
|
|
||||||
**exifInfoPeriodCity** | **String**| | [optional]
|
|
||||||
**exifInfoPeriodState** | **String**| | [optional]
|
|
||||||
**exifInfoPeriodCountry** | **String**| | [optional]
|
|
||||||
**exifInfoPeriodMake** | **String**| | [optional]
|
|
||||||
**exifInfoPeriodModel** | **String**| | [optional]
|
|
||||||
**exifInfoPeriodProjectionType** | **String**| | [optional]
|
|
||||||
**smartInfoPeriodObjects** | [**List<String>**](String.md)| | [optional] [default to const []]
|
|
||||||
**smartInfoPeriodTags** | [**List<String>**](String.md)| | [optional] [default to const []]
|
|
||||||
**recent** | **bool**| | [optional]
|
**recent** | **bool**| | [optional]
|
||||||
**motion** | **bool**| | [optional]
|
**motion** | **bool**| | [optional]
|
||||||
|
|
||||||
|
|
76
mobile/openapi/lib/api/search_api.dart
generated
76
mobile/openapi/lib/api/search_api.dart
generated
|
@ -71,30 +71,10 @@ class SearchApi {
|
||||||
///
|
///
|
||||||
/// * [String] type:
|
/// * [String] type:
|
||||||
///
|
///
|
||||||
/// * [bool] isFavorite:
|
|
||||||
///
|
|
||||||
/// * [bool] isArchived:
|
|
||||||
///
|
|
||||||
/// * [String] exifInfoPeriodCity:
|
|
||||||
///
|
|
||||||
/// * [String] exifInfoPeriodState:
|
|
||||||
///
|
|
||||||
/// * [String] exifInfoPeriodCountry:
|
|
||||||
///
|
|
||||||
/// * [String] exifInfoPeriodMake:
|
|
||||||
///
|
|
||||||
/// * [String] exifInfoPeriodModel:
|
|
||||||
///
|
|
||||||
/// * [String] exifInfoPeriodProjectionType:
|
|
||||||
///
|
|
||||||
/// * [List<String>] smartInfoPeriodObjects:
|
|
||||||
///
|
|
||||||
/// * [List<String>] smartInfoPeriodTags:
|
|
||||||
///
|
|
||||||
/// * [bool] recent:
|
/// * [bool] recent:
|
||||||
///
|
///
|
||||||
/// * [bool] motion:
|
/// * [bool] motion:
|
||||||
Future<Response> searchWithHttpInfo({ String? q, String? query, bool? clip, String? type, bool? isFavorite, bool? isArchived, String? exifInfoPeriodCity, String? exifInfoPeriodState, String? exifInfoPeriodCountry, String? exifInfoPeriodMake, String? exifInfoPeriodModel, String? exifInfoPeriodProjectionType, List<String>? smartInfoPeriodObjects, List<String>? smartInfoPeriodTags, bool? recent, bool? motion, }) async {
|
Future<Response> searchWithHttpInfo({ String? q, String? query, bool? clip, String? type, bool? recent, bool? motion, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final path = r'/search';
|
final path = r'/search';
|
||||||
|
|
||||||
|
@ -117,36 +97,6 @@ class SearchApi {
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
queryParams.addAll(_queryParams('', 'type', type));
|
queryParams.addAll(_queryParams('', 'type', type));
|
||||||
}
|
}
|
||||||
if (isFavorite != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
|
||||||
}
|
|
||||||
if (isArchived != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
|
|
||||||
}
|
|
||||||
if (exifInfoPeriodCity != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'exifInfo.city', exifInfoPeriodCity));
|
|
||||||
}
|
|
||||||
if (exifInfoPeriodState != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'exifInfo.state', exifInfoPeriodState));
|
|
||||||
}
|
|
||||||
if (exifInfoPeriodCountry != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'exifInfo.country', exifInfoPeriodCountry));
|
|
||||||
}
|
|
||||||
if (exifInfoPeriodMake != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'exifInfo.make', exifInfoPeriodMake));
|
|
||||||
}
|
|
||||||
if (exifInfoPeriodModel != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'exifInfo.model', exifInfoPeriodModel));
|
|
||||||
}
|
|
||||||
if (exifInfoPeriodProjectionType != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'exifInfo.projectionType', exifInfoPeriodProjectionType));
|
|
||||||
}
|
|
||||||
if (smartInfoPeriodObjects != null) {
|
|
||||||
queryParams.addAll(_queryParams('multi', 'smartInfo.objects', smartInfoPeriodObjects));
|
|
||||||
}
|
|
||||||
if (smartInfoPeriodTags != null) {
|
|
||||||
queryParams.addAll(_queryParams('multi', 'smartInfo.tags', smartInfoPeriodTags));
|
|
||||||
}
|
|
||||||
if (recent != null) {
|
if (recent != null) {
|
||||||
queryParams.addAll(_queryParams('', 'recent', recent));
|
queryParams.addAll(_queryParams('', 'recent', recent));
|
||||||
}
|
}
|
||||||
|
@ -178,31 +128,11 @@ class SearchApi {
|
||||||
///
|
///
|
||||||
/// * [String] type:
|
/// * [String] type:
|
||||||
///
|
///
|
||||||
/// * [bool] isFavorite:
|
|
||||||
///
|
|
||||||
/// * [bool] isArchived:
|
|
||||||
///
|
|
||||||
/// * [String] exifInfoPeriodCity:
|
|
||||||
///
|
|
||||||
/// * [String] exifInfoPeriodState:
|
|
||||||
///
|
|
||||||
/// * [String] exifInfoPeriodCountry:
|
|
||||||
///
|
|
||||||
/// * [String] exifInfoPeriodMake:
|
|
||||||
///
|
|
||||||
/// * [String] exifInfoPeriodModel:
|
|
||||||
///
|
|
||||||
/// * [String] exifInfoPeriodProjectionType:
|
|
||||||
///
|
|
||||||
/// * [List<String>] smartInfoPeriodObjects:
|
|
||||||
///
|
|
||||||
/// * [List<String>] smartInfoPeriodTags:
|
|
||||||
///
|
|
||||||
/// * [bool] recent:
|
/// * [bool] recent:
|
||||||
///
|
///
|
||||||
/// * [bool] motion:
|
/// * [bool] motion:
|
||||||
Future<SearchResponseDto?> search({ String? q, String? query, bool? clip, String? type, bool? isFavorite, bool? isArchived, String? exifInfoPeriodCity, String? exifInfoPeriodState, String? exifInfoPeriodCountry, String? exifInfoPeriodMake, String? exifInfoPeriodModel, String? exifInfoPeriodProjectionType, List<String>? smartInfoPeriodObjects, List<String>? smartInfoPeriodTags, bool? recent, bool? motion, }) async {
|
Future<SearchResponseDto?> search({ String? q, String? query, bool? clip, String? type, bool? recent, bool? motion, }) async {
|
||||||
final response = await searchWithHttpInfo( q: q, query: query, clip: clip, type: type, isFavorite: isFavorite, isArchived: isArchived, exifInfoPeriodCity: exifInfoPeriodCity, exifInfoPeriodState: exifInfoPeriodState, exifInfoPeriodCountry: exifInfoPeriodCountry, exifInfoPeriodMake: exifInfoPeriodMake, exifInfoPeriodModel: exifInfoPeriodModel, exifInfoPeriodProjectionType: exifInfoPeriodProjectionType, smartInfoPeriodObjects: smartInfoPeriodObjects, smartInfoPeriodTags: smartInfoPeriodTags, recent: recent, motion: motion, );
|
final response = await searchWithHttpInfo( q: q, query: query, clip: clip, type: type, recent: recent, motion: motion, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
|
|
2
mobile/openapi/test/search_api_test.dart
generated
2
mobile/openapi/test/search_api_test.dart
generated
|
@ -22,7 +22,7 @@ void main() {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
//Future<SearchResponseDto> search({ String q, String query, bool clip, String type, bool isFavorite, bool isArchived, String exifInfoPeriodCity, String exifInfoPeriodState, String exifInfoPeriodCountry, String exifInfoPeriodMake, String exifInfoPeriodModel, String exifInfoPeriodProjectionType, List<String> smartInfoPeriodObjects, List<String> smartInfoPeriodTags, bool recent, bool motion }) async
|
//Future<SearchResponseDto> search({ String q, String query, bool clip, String type, bool recent, bool motion }) async
|
||||||
test('test search', () async {
|
test('test search', () async {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
|
@ -4567,92 +4567,6 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "isFavorite",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "isArchived",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "exifInfo.city",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "exifInfo.state",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "exifInfo.country",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "exifInfo.make",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "exifInfo.model",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "exifInfo.projectionType",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "smartInfo.objects",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "smartInfo.tags",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "recent",
|
"name": "recent",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AssetEntity, AssetFaceEntity, AssetType } from '@app/infra/entities';
|
import { AssetEntity, AssetType } from '@app/infra/entities';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { PersonWithFacesResponseDto } from '../../person/person.dto';
|
import { PersonWithFacesResponseDto } from '../../person/person.dto';
|
||||||
import { TagResponseDto, mapTag } from '../../tag';
|
import { TagResponseDto, mapTag } from '../../tag';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { SystemConfig, SystemConfigEntity, SystemConfigKey } from '@app/infra/entities';
|
import { SystemConfig, SystemConfigKey } from '@app/infra/entities';
|
||||||
import { BadRequestException } from '@nestjs/common';
|
import { BadRequestException } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
assetStub,
|
assetStub,
|
||||||
|
@ -362,7 +362,7 @@ describe(JobService.name, () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const featureTests: Array<{ queue: QueueName, feature: FeatureFlag, configKey: SystemConfigKey}> = [
|
const featureTests: Array<{ queue: QueueName; feature: FeatureFlag; configKey: SystemConfigKey }> = [
|
||||||
{
|
{
|
||||||
queue: QueueName.CLIP_ENCODING,
|
queue: QueueName.CLIP_ENCODING,
|
||||||
feature: FeatureFlag.CLIP_ENCODE,
|
feature: FeatureFlag.CLIP_ENCODE,
|
||||||
|
@ -385,7 +385,7 @@ describe(JobService.name, () => {
|
||||||
configMock.load.mockResolvedValue([{ key: configKey, value: false }]);
|
configMock.load.mockResolvedValue([{ key: configKey, value: false }]);
|
||||||
jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
||||||
|
|
||||||
expect(sut.handleCommand(queue, { command: JobCommand.START, force: false })).rejects.toThrow();
|
await expect(sut.handleCommand(queue, { command: JobCommand.START, force: false })).rejects.toThrow();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { SearchExploreItem } from '@app/domain';
|
import { SearchExploreItem } from '@app/domain';
|
||||||
import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity, SmartInfoEntity } from '@app/infra/entities';
|
import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '@app/infra/entities';
|
||||||
import { FindOptionsRelations } from 'typeorm';
|
import { FindOptionsRelations } from 'typeorm';
|
||||||
import { Paginated, PaginationOptions } from '../domain.util';
|
import { Paginated, PaginationOptions } from '../domain.util';
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { AssetEntity, AssetFaceEntity, SmartInfoEntity } from '@app/infra/entiti
|
||||||
|
|
||||||
export const ISmartInfoRepository = 'ISmartInfoRepository';
|
export const ISmartInfoRepository = 'ISmartInfoRepository';
|
||||||
|
|
||||||
export type Embedding = number[];
|
export type Embedding = number[];
|
||||||
|
|
||||||
export interface EmbeddingSearch {
|
export interface EmbeddingSearch {
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
import { AssetType } from '@app/infra/entities';
|
||||||
import { Transform } from 'class-transformer';
|
import { Transform } from 'class-transformer';
|
||||||
import { IsBoolean, IsNotEmpty, IsString } from 'class-validator';
|
import { IsBoolean, IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||||
import { Optional, toBoolean } from '../../domain.util';
|
import { Optional, toBoolean } from '../../domain.util';
|
||||||
|
|
||||||
export class SearchDto {
|
export class SearchDto {
|
||||||
|
@ -17,6 +18,20 @@ export class SearchDto {
|
||||||
@Optional()
|
@Optional()
|
||||||
@Transform(toBoolean)
|
@Transform(toBoolean)
|
||||||
clip?: boolean;
|
clip?: boolean;
|
||||||
|
|
||||||
|
@IsEnum(AssetType)
|
||||||
|
@Optional()
|
||||||
|
type?: AssetType;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@Optional()
|
||||||
|
@Transform(toBoolean)
|
||||||
|
recent?: boolean;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@Optional()
|
||||||
|
@Transform(toBoolean)
|
||||||
|
motion?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SearchPeopleDto {
|
export class SearchPeopleDto {
|
||||||
|
|
|
@ -80,7 +80,7 @@ export class SearchService {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
albums: {
|
albums: {
|
||||||
total: 0,
|
total: 0,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { dataSource } from '@app/infra';
|
||||||
import AsyncLock from 'async-lock';
|
import AsyncLock from 'async-lock';
|
||||||
export enum DatabaseLock {
|
export enum DatabaseLock {
|
||||||
GeodataImport = 100,
|
GeodataImport = 100,
|
||||||
CLIPDimSize = 512
|
CLIPDimSize = 512,
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function acquireLock(lock: DatabaseLock): Promise<void> {
|
export async function acquireLock(lock: DatabaseLock): Promise<void> {
|
||||||
|
@ -15,10 +15,12 @@ export async function releaseLock(lock: DatabaseLock): Promise<void> {
|
||||||
|
|
||||||
export const asyncLock = new AsyncLock();
|
export const asyncLock = new AsyncLock();
|
||||||
|
|
||||||
export function RequireLock<T>(lock: DatabaseLock): Function {
|
export function RequireLock<T>(
|
||||||
|
lock: DatabaseLock,
|
||||||
|
): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void {
|
||||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): void {
|
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): void {
|
||||||
const originalMethod = descriptor.value;
|
const originalMethod = descriptor.value;
|
||||||
descriptor.value = async function(...args: any[]): Promise<T> {
|
descriptor.value = async function (...args: any[]): Promise<T> {
|
||||||
if (!dataSource.isInitialized) {
|
if (!dataSource.isInitialized) {
|
||||||
await dataSource.initialize();
|
await dataSource.initialize();
|
||||||
}
|
}
|
||||||
|
@ -35,5 +37,5 @@ export function RequireLock<T>(lock: DatabaseLock): Function {
|
||||||
|
|
||||||
return res as any;
|
return res as any;
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export * from './database.config';
|
|
||||||
export * from './database-locks';
|
export * from './database-locks';
|
||||||
|
export * from './database.config';
|
||||||
export * from './infra.config';
|
export * from './infra.config';
|
||||||
export * from './infra.module';
|
export * from './infra.module';
|
||||||
export * from './redis-io.adapter';
|
export * from './redis-io.adapter';
|
||||||
|
|
|
@ -53,8 +53,8 @@ import {
|
||||||
MoveRepository,
|
MoveRepository,
|
||||||
PartnerRepository,
|
PartnerRepository,
|
||||||
PersonRepository,
|
PersonRepository,
|
||||||
ServerInfoRepository,
|
|
||||||
SearchRepository,
|
SearchRepository,
|
||||||
|
ServerInfoRepository,
|
||||||
SharedLinkRepository,
|
SharedLinkRepository,
|
||||||
SmartInfoRepository,
|
SmartInfoRepository,
|
||||||
SystemConfigRepository,
|
SystemConfigRepository,
|
||||||
|
|
|
@ -36,7 +36,7 @@ export async function paginate<Entity extends ObjectLiteral>(
|
||||||
export const asVector = (embedding: number[], quote = false) =>
|
export const asVector = (embedding: number[], quote = false) =>
|
||||||
quote ? `'[${embedding.join(',')}]'` : `[${embedding.join(',')}]`;
|
quote ? `'[${embedding.join(',')}]'` : `[${embedding.join(',')}]`;
|
||||||
|
|
||||||
export const isValidInteger = (value: number, options: {min?: number, max?: number}): value is number => {
|
export const isValidInteger = (value: number, options: { min?: number; max?: number }): value is number => {
|
||||||
const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = options;
|
const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = options;
|
||||||
return Number.isInteger(value) && value >= min && value <= max;
|
return Number.isInteger(value) && value >= min && value <= max;
|
||||||
}
|
};
|
||||||
|
|
|
@ -5,8 +5,8 @@ import {
|
||||||
ISystemMetadataRepository,
|
ISystemMetadataRepository,
|
||||||
ReverseGeocodeResult,
|
ReverseGeocodeResult,
|
||||||
} from '@app/domain';
|
} from '@app/domain';
|
||||||
import { GeodataAdmin1Entity, GeodataAdmin2Entity, GeodataPlacesEntity, SystemMetadataKey } from '@app/infra/entities';
|
|
||||||
import { DatabaseLock, RequireLock } from '@app/infra';
|
import { DatabaseLock, RequireLock } from '@app/infra';
|
||||||
|
import { GeodataAdmin1Entity, GeodataAdmin2Entity, GeodataPlacesEntity, SystemMetadataKey } from '@app/infra/entities';
|
||||||
import { Inject, Logger } from '@nestjs/common';
|
import { Inject, Logger } from '@nestjs/common';
|
||||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||||
import { DefaultReadTaskOptions, exiftool, Tags } from 'exiftool-vendored';
|
import { DefaultReadTaskOptions, exiftool, Tags } from 'exiftool-vendored';
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Embedding, EmbeddingSearch, ISmartInfoRepository } from '@app/domain';
|
import { Embedding, EmbeddingSearch, ISmartInfoRepository } from '@app/domain';
|
||||||
|
import { DatabaseLock, RequireLock, asyncLock } from '@app/infra';
|
||||||
|
import { AssetEntity, AssetFaceEntity, SmartInfoEntity, SmartSearchEntity } from '@app/infra/entities';
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { AssetEntity, AssetFaceEntity, SmartInfoEntity, SmartSearchEntity } from '@app/infra/entities';
|
|
||||||
import { asVector, isValidInteger } from '../infra.utils';
|
import { asVector, isValidInteger } from '../infra.utils';
|
||||||
import { DatabaseLock, RequireLock, asyncLock } from '@app/infra';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SmartInfoRepository implements ISmartInfoRepository {
|
export class SmartInfoRepository implements ISmartInfoRepository {
|
||||||
|
@ -46,12 +46,7 @@ export class SmartInfoRepository implements ISmartInfoRepository {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchFaces({
|
async searchFaces({ ownerId, embedding, numResults, maxDistance }: EmbeddingSearch): Promise<AssetFaceEntity[]> {
|
||||||
ownerId,
|
|
||||||
embedding,
|
|
||||||
numResults,
|
|
||||||
maxDistance,
|
|
||||||
}: EmbeddingSearch): Promise<AssetFaceEntity[]> {
|
|
||||||
if (!isValidInteger(numResults, { min: 1 })) {
|
if (!isValidInteger(numResults, { min: 1 })) {
|
||||||
throw new Error(`Invalid value for 'numResults': ${numResults}`);
|
throw new Error(`Invalid value for 'numResults': ${numResults}`);
|
||||||
}
|
}
|
||||||
|
@ -84,7 +79,9 @@ export class SmartInfoRepository implements ISmartInfoRepository {
|
||||||
|
|
||||||
async upsert(smartInfo: Partial<SmartInfoEntity>, embedding?: Embedding): Promise<void> {
|
async upsert(smartInfo: Partial<SmartInfoEntity>, embedding?: Embedding): Promise<void> {
|
||||||
await this.repository.upsert(smartInfo, { conflictPaths: ['assetId'] });
|
await this.repository.upsert(smartInfo, { conflictPaths: ['assetId'] });
|
||||||
if (!smartInfo.assetId || !embedding) return;
|
if (!smartInfo.assetId || !embedding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.upsertEmbedding(smartInfo.assetId, embedding);
|
await this.upsertEmbedding(smartInfo.assetId, embedding);
|
||||||
}
|
}
|
||||||
|
@ -115,8 +112,10 @@ export class SmartInfoRepository implements ISmartInfoRepository {
|
||||||
throw new Error(`Invalid CLIP dimension size: ${dimSize}`);
|
throw new Error(`Invalid CLIP dimension size: ${dimSize}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.curDimSize === dimSize) return;
|
if (this.curDimSize === dimSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.log(`Current dimension size is ${this.curDimSize}. Updating CLIP dimension size to ${dimSize}.`);
|
this.logger.log(`Current dimension size is ${this.curDimSize}. Updating CLIP dimension size to ${dimSize}.`);
|
||||||
|
|
||||||
await this.smartSearchRepository.manager.transaction(async (manager) => {
|
await this.smartSearchRepository.manager.transaction(async (manager) => {
|
||||||
|
@ -142,7 +141,9 @@ export class SmartInfoRepository implements ISmartInfoRepository {
|
||||||
|
|
||||||
@RequireLock(DatabaseLock.CLIPDimSize)
|
@RequireLock(DatabaseLock.CLIPDimSize)
|
||||||
private async getDimSize(): Promise<void> {
|
private async getDimSize(): Promise<void> {
|
||||||
if (this.curDimSize != null) return;
|
if (this.curDimSize != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const res = await this.smartSearchRepository.manager.query(`
|
const res = await this.smartSearchRepository.manager.query(`
|
||||||
SELECT atttypmod as dimsize
|
SELECT atttypmod as dimsize
|
||||||
|
|
|
@ -37,7 +37,11 @@ describe(`${PersonController.name}`, () => {
|
||||||
name: 'visible_person',
|
name: 'visible_person',
|
||||||
thumbnailPath: '/thumbnail/face_asset',
|
thumbnailPath: '/thumbnail/face_asset',
|
||||||
});
|
});
|
||||||
await personRepository.createFace({ assetId: faceAsset.id, personId: visiblePerson.id, embedding: Array.from({length: 512}, Math.random) });
|
await personRepository.createFace({
|
||||||
|
assetId: faceAsset.id,
|
||||||
|
personId: visiblePerson.id,
|
||||||
|
embedding: Array.from({ length: 512 }, Math.random),
|
||||||
|
});
|
||||||
|
|
||||||
hiddenPerson = await personRepository.create({
|
hiddenPerson = await personRepository.create({
|
||||||
ownerId: loginResponse.userId,
|
ownerId: loginResponse.userId,
|
||||||
|
@ -45,7 +49,11 @@ describe(`${PersonController.name}`, () => {
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
thumbnailPath: '/thumbnail/face_asset',
|
thumbnailPath: '/thumbnail/face_asset',
|
||||||
});
|
});
|
||||||
await personRepository.createFace({ assetId: faceAsset.id, personId: hiddenPerson.id, embedding: Array.from({length: 512}, Math.random) });
|
await personRepository.createFace({
|
||||||
|
assetId: faceAsset.id,
|
||||||
|
personId: hiddenPerson.id,
|
||||||
|
embedding: Array.from({ length: 512 }, Math.random),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /person', () => {
|
describe('GET /person', () => {
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
import { AssetResponseDto, IAssetRepository, ISmartInfoRepository, LibraryResponseDto, LoginResponseDto, mapAsset } from '@app/domain';
|
import {
|
||||||
|
AssetResponseDto,
|
||||||
|
IAssetRepository,
|
||||||
|
ISmartInfoRepository,
|
||||||
|
LibraryResponseDto,
|
||||||
|
LoginResponseDto,
|
||||||
|
mapAsset,
|
||||||
|
} from '@app/domain';
|
||||||
import { SearchController } from '@app/immich';
|
import { SearchController } from '@app/immich';
|
||||||
import { INestApplication } from '@nestjs/common';
|
import { INestApplication } from '@nestjs/common';
|
||||||
import { api } from '@test/api';
|
import { api } from '@test/api';
|
||||||
|
@ -47,7 +54,8 @@ describe(`${SearchController.name}`, () => {
|
||||||
});
|
});
|
||||||
await smartInfoRepository.upsert(
|
await smartInfoRepository.upsert(
|
||||||
{ assetId, objects: ['car', 'tree'], tags: ['accident'] },
|
{ assetId, objects: ['car', 'tree'], tags: ['accident'] },
|
||||||
Array.from({ length: 512 }, Math.random));
|
Array.from({ length: 512 }, Math.random),
|
||||||
|
);
|
||||||
const assetWithMetadata = await assetRepository.getById(assetId, { exifInfo: true, smartInfo: true });
|
const assetWithMetadata = await assetRepository.getById(assetId, { exifInfo: true, smartInfo: true });
|
||||||
if (!assetWithMetadata) {
|
if (!assetWithMetadata) {
|
||||||
throw new Error('Asset not found');
|
throw new Error('Asset not found');
|
||||||
|
|
Loading…
Reference in a new issue