formatting

This commit is contained in:
mertalev 2023-12-03 19:23:21 -05:00
parent 436f99c747
commit 1338c5fd59
No known key found for this signature in database
GPG key ID: 9181CD92C0A1C5E3
19 changed files with 75 additions and 217 deletions

View file

@ -213,7 +213,7 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: tensorchord/pgvecto-rs:pg14-v0.1.10
image: tensorchord/pgvecto-rs:pg14-v0.1.11
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres

View file

@ -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)
# **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 clip = true; // bool |
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 motion = true; // bool |
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);
} catch (e) {
print('Exception when calling SearchApi->search: $e\n');
@ -122,16 +112,6 @@ Name | Type | Description | Notes
**query** | **String**| | [optional]
**clip** | **bool**| | [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]
**motion** | **bool**| | [optional]

View file

@ -71,30 +71,10 @@ class SearchApi {
///
/// * [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:
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
final path = r'/search';
@ -117,36 +97,6 @@ class SearchApi {
if (type != null) {
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) {
queryParams.addAll(_queryParams('', 'recent', recent));
}
@ -178,31 +128,11 @@ class SearchApi {
///
/// * [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:
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 {
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, );
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, recent: recent, motion: motion, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View file

@ -22,7 +22,7 @@ void main() {
// 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 {
// TODO
});

View file

@ -4567,92 +4567,6 @@
"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",
"required": false,

View file

@ -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 { PersonWithFacesResponseDto } from '../../person/person.dto';
import { TagResponseDto, mapTag } from '../../tag';

View file

@ -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 {
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,
feature: FeatureFlag.CLIP_ENCODE,
@ -385,7 +385,7 @@ describe(JobService.name, () => {
configMock.load.mockResolvedValue([{ key: configKey, value: 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();
});
}
});

View file

@ -1,5 +1,5 @@
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 { Paginated, PaginationOptions } from '../domain.util';

View file

@ -2,7 +2,7 @@ import { AssetEntity, AssetFaceEntity, SmartInfoEntity } from '@app/infra/entiti
export const ISmartInfoRepository = 'ISmartInfoRepository';
export type Embedding = number[];
export type Embedding = number[];
export interface EmbeddingSearch {
ownerId: string;

View file

@ -1,5 +1,6 @@
import { AssetType } from '@app/infra/entities';
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';
export class SearchDto {
@ -17,6 +18,20 @@ export class SearchDto {
@Optional()
@Transform(toBoolean)
clip?: boolean;
@IsEnum(AssetType)
@Optional()
type?: AssetType;
@IsBoolean()
@Optional()
@Transform(toBoolean)
recent?: boolean;
@IsBoolean()
@Optional()
@Transform(toBoolean)
motion?: boolean;
}
export class SearchPeopleDto {

View file

@ -80,7 +80,7 @@ export class SearchService {
default:
break;
}
return {
albums: {
total: 0,

View file

@ -2,7 +2,7 @@ import { dataSource } from '@app/infra';
import AsyncLock from 'async-lock';
export enum DatabaseLock {
GeodataImport = 100,
CLIPDimSize = 512
CLIPDimSize = 512,
}
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 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 {
const originalMethod = descriptor.value;
descriptor.value = async function(...args: any[]): Promise<T> {
descriptor.value = async function (...args: any[]): Promise<T> {
if (!dataSource.isInitialized) {
await dataSource.initialize();
}
@ -35,5 +37,5 @@ export function RequireLock<T>(lock: DatabaseLock): Function {
return res as any;
};
}
}
};
}

View file

@ -1,5 +1,5 @@
export * from './database.config';
export * from './database-locks';
export * from './database.config';
export * from './infra.config';
export * from './infra.module';
export * from './redis-io.adapter';

View file

@ -53,8 +53,8 @@ import {
MoveRepository,
PartnerRepository,
PersonRepository,
ServerInfoRepository,
SearchRepository,
ServerInfoRepository,
SharedLinkRepository,
SmartInfoRepository,
SystemConfigRepository,

View file

@ -36,7 +36,7 @@ export async function paginate<Entity extends ObjectLiteral>(
export const asVector = (embedding: number[], quote = false) =>
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;
return Number.isInteger(value) && value >= min && value <= max;
}
};

View file

@ -5,8 +5,8 @@ import {
ISystemMetadataRepository,
ReverseGeocodeResult,
} from '@app/domain';
import { GeodataAdmin1Entity, GeodataAdmin2Entity, GeodataPlacesEntity, SystemMetadataKey } from '@app/infra/entities';
import { DatabaseLock, RequireLock } from '@app/infra';
import { GeodataAdmin1Entity, GeodataAdmin2Entity, GeodataPlacesEntity, SystemMetadataKey } from '@app/infra/entities';
import { Inject, Logger } from '@nestjs/common';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { DefaultReadTaskOptions, exiftool, Tags } from 'exiftool-vendored';

View file

@ -1,10 +1,10 @@
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 { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AssetEntity, AssetFaceEntity, SmartInfoEntity, SmartSearchEntity } from '@app/infra/entities';
import { asVector, isValidInteger } from '../infra.utils';
import { DatabaseLock, RequireLock, asyncLock } from '@app/infra';
@Injectable()
export class SmartInfoRepository implements ISmartInfoRepository {
@ -46,12 +46,7 @@ export class SmartInfoRepository implements ISmartInfoRepository {
return results;
}
async searchFaces({
ownerId,
embedding,
numResults,
maxDistance,
}: EmbeddingSearch): Promise<AssetFaceEntity[]> {
async searchFaces({ ownerId, embedding, numResults, maxDistance }: EmbeddingSearch): Promise<AssetFaceEntity[]> {
if (!isValidInteger(numResults, { min: 1 })) {
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> {
await this.repository.upsert(smartInfo, { conflictPaths: ['assetId'] });
if (!smartInfo.assetId || !embedding) return;
if (!smartInfo.assetId || !embedding) {
return;
}
await this.upsertEmbedding(smartInfo.assetId, embedding);
}
@ -115,8 +112,10 @@ export class SmartInfoRepository implements ISmartInfoRepository {
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}.`);
await this.smartSearchRepository.manager.transaction(async (manager) => {
@ -142,7 +141,9 @@ export class SmartInfoRepository implements ISmartInfoRepository {
@RequireLock(DatabaseLock.CLIPDimSize)
private async getDimSize(): Promise<void> {
if (this.curDimSize != null) return;
if (this.curDimSize != null) {
return;
}
const res = await this.smartSearchRepository.manager.query(`
SELECT atttypmod as dimsize

View file

@ -37,7 +37,11 @@ describe(`${PersonController.name}`, () => {
name: 'visible_person',
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({
ownerId: loginResponse.userId,
@ -45,7 +49,11 @@ describe(`${PersonController.name}`, () => {
isHidden: true,
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', () => {

View file

@ -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 { INestApplication } from '@nestjs/common';
import { api } from '@test/api';
@ -47,7 +54,8 @@ describe(`${SearchController.name}`, () => {
});
await smartInfoRepository.upsert(
{ 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 });
if (!assetWithMetadata) {
throw new Error('Asset not found');