mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 22:51:59 +00:00
refactor: remove smart info table (#13985)
This commit is contained in:
parent
6053214e75
commit
64831e2328
25 changed files with 15 additions and 206 deletions
|
@ -473,10 +473,7 @@ describe('/search', () => {
|
||||||
.get('/search/explore')
|
.get('/search/explore')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([{ fieldName: 'exifInfo.city', items: [] }]);
|
||||||
{ fieldName: 'exifInfo.city', items: [] },
|
|
||||||
{ fieldName: 'smartInfo.tags', items: [] },
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
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_client.dart
generated
BIN
mobile/openapi/lib/api_client.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/model/asset_response_dto.dart
generated
BIN
mobile/openapi/lib/model/asset_response_dto.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/model/smart_info_response_dto.dart
generated
BIN
mobile/openapi/lib/model/smart_info_response_dto.dart
generated
Binary file not shown.
|
@ -8402,9 +8402,6 @@
|
||||||
"description": "This property was deprecated in v1.113.0",
|
"description": "This property was deprecated in v1.113.0",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"smartInfo": {
|
|
||||||
"$ref": "#/components/schemas/SmartInfoResponseDto"
|
|
||||||
},
|
|
||||||
"stack": {
|
"stack": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
|
@ -11284,25 +11281,6 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"SmartInfoResponseDto": {
|
|
||||||
"properties": {
|
|
||||||
"objects": {
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"nullable": true,
|
|
||||||
"type": "array"
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"nullable": true,
|
|
||||||
"type": "array"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"SmartSearchDto": {
|
"SmartSearchDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"city": {
|
"city": {
|
||||||
|
|
|
@ -221,10 +221,6 @@ export type PersonWithFacesResponseDto = {
|
||||||
/** This property was added in v1.107.0 */
|
/** This property was added in v1.107.0 */
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
};
|
};
|
||||||
export type SmartInfoResponseDto = {
|
|
||||||
objects?: string[] | null;
|
|
||||||
tags?: string[] | null;
|
|
||||||
};
|
|
||||||
export type AssetStackResponseDto = {
|
export type AssetStackResponseDto = {
|
||||||
assetCount: number;
|
assetCount: number;
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -267,7 +263,6 @@ export type AssetResponseDto = {
|
||||||
people?: PersonWithFacesResponseDto[];
|
people?: PersonWithFacesResponseDto[];
|
||||||
/** This property was deprecated in v1.113.0 */
|
/** This property was deprecated in v1.113.0 */
|
||||||
resized?: boolean;
|
resized?: boolean;
|
||||||
smartInfo?: SmartInfoResponseDto;
|
|
||||||
stack?: (AssetStackResponseDto) | null;
|
stack?: (AssetStackResponseDto) | null;
|
||||||
tags?: TagResponseDto[];
|
tags?: TagResponseDto[];
|
||||||
thumbhash: string | null;
|
thumbhash: string | null;
|
||||||
|
|
|
@ -12,7 +12,6 @@ import { TagResponseDto, mapTag } from 'src/dtos/tag.dto';
|
||||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
||||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { SmartInfoEntity } from 'src/entities/smart-info.entity';
|
|
||||||
import { AssetType } from 'src/enum';
|
import { AssetType } from 'src/enum';
|
||||||
import { mimeTypes } from 'src/utils/mime-types';
|
import { mimeTypes } from 'src/utils/mime-types';
|
||||||
|
|
||||||
|
@ -45,7 +44,6 @@ export class AssetResponseDto extends SanitizedAssetResponseDto {
|
||||||
isTrashed!: boolean;
|
isTrashed!: boolean;
|
||||||
isOffline!: boolean;
|
isOffline!: boolean;
|
||||||
exifInfo?: ExifResponseDto;
|
exifInfo?: ExifResponseDto;
|
||||||
smartInfo?: SmartInfoResponseDto;
|
|
||||||
tags?: TagResponseDto[];
|
tags?: TagResponseDto[];
|
||||||
people?: PersonWithFacesResponseDto[];
|
people?: PersonWithFacesResponseDto[];
|
||||||
unassignedFaces?: AssetFaceWithoutPersonResponseDto[];
|
unassignedFaces?: AssetFaceWithoutPersonResponseDto[];
|
||||||
|
@ -141,7 +139,6 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
|
||||||
isTrashed: !!entity.deletedAt,
|
isTrashed: !!entity.deletedAt,
|
||||||
duration: entity.duration ?? '0:00:00.00000',
|
duration: entity.duration ?? '0:00:00.00000',
|
||||||
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
|
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
|
||||||
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
|
||||||
livePhotoVideoId: entity.livePhotoVideoId,
|
livePhotoVideoId: entity.livePhotoVideoId,
|
||||||
tags: entity.tags?.map((tag) => mapTag(tag)),
|
tags: entity.tags?.map((tag) => mapTag(tag)),
|
||||||
people: peopleWithFaces(entity.faces),
|
people: peopleWithFaces(entity.faces),
|
||||||
|
@ -161,15 +158,3 @@ export class MemoryLaneResponseDto {
|
||||||
|
|
||||||
assets!: AssetResponseDto[];
|
assets!: AssetResponseDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SmartInfoResponseDto {
|
|
||||||
tags?: string[] | null;
|
|
||||||
objects?: string[] | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mapSmartInfo(entity: SmartInfoEntity): SmartInfoResponseDto {
|
|
||||||
return {
|
|
||||||
tags: entity.tags,
|
|
||||||
objects: entity.objects,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
|
||||||
import { ExifEntity } from 'src/entities/exif.entity';
|
import { ExifEntity } from 'src/entities/exif.entity';
|
||||||
import { LibraryEntity } from 'src/entities/library.entity';
|
import { LibraryEntity } from 'src/entities/library.entity';
|
||||||
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||||
import { SmartInfoEntity } from 'src/entities/smart-info.entity';
|
|
||||||
import { SmartSearchEntity } from 'src/entities/smart-search.entity';
|
import { SmartSearchEntity } from 'src/entities/smart-search.entity';
|
||||||
import { StackEntity } from 'src/entities/stack.entity';
|
import { StackEntity } from 'src/entities/stack.entity';
|
||||||
import { TagEntity } from 'src/entities/tag.entity';
|
import { TagEntity } from 'src/entities/tag.entity';
|
||||||
|
@ -143,9 +142,6 @@ export class AssetEntity {
|
||||||
@OneToOne(() => ExifEntity, (exifEntity) => exifEntity.asset)
|
@OneToOne(() => ExifEntity, (exifEntity) => exifEntity.asset)
|
||||||
exifInfo?: ExifEntity;
|
exifInfo?: ExifEntity;
|
||||||
|
|
||||||
@OneToOne(() => SmartInfoEntity, (smartInfoEntity) => smartInfoEntity.asset)
|
|
||||||
smartInfo?: SmartInfoEntity;
|
|
||||||
|
|
||||||
@OneToOne(() => SmartSearchEntity, (smartSearchEntity) => smartSearchEntity.asset)
|
@OneToOne(() => SmartSearchEntity, (smartSearchEntity) => smartSearchEntity.asset)
|
||||||
smartSearch?: SmartSearchEntity;
|
smartSearch?: SmartSearchEntity;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ import { PartnerEntity } from 'src/entities/partner.entity';
|
||||||
import { PersonEntity } from 'src/entities/person.entity';
|
import { PersonEntity } from 'src/entities/person.entity';
|
||||||
import { SessionEntity } from 'src/entities/session.entity';
|
import { SessionEntity } from 'src/entities/session.entity';
|
||||||
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||||
import { SmartInfoEntity } from 'src/entities/smart-info.entity';
|
|
||||||
import { SmartSearchEntity } from 'src/entities/smart-search.entity';
|
import { SmartSearchEntity } from 'src/entities/smart-search.entity';
|
||||||
import { StackEntity } from 'src/entities/stack.entity';
|
import { StackEntity } from 'src/entities/stack.entity';
|
||||||
import { SystemMetadataEntity } from 'src/entities/system-metadata.entity';
|
import { SystemMetadataEntity } from 'src/entities/system-metadata.entity';
|
||||||
|
@ -46,7 +45,6 @@ export const entities = [
|
||||||
PartnerEntity,
|
PartnerEntity,
|
||||||
PersonEntity,
|
PersonEntity,
|
||||||
SharedLinkEntity,
|
SharedLinkEntity,
|
||||||
SmartInfoEntity,
|
|
||||||
SmartSearchEntity,
|
SmartSearchEntity,
|
||||||
StackEntity,
|
StackEntity,
|
||||||
SystemMetadataEntity,
|
SystemMetadataEntity,
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
|
||||||
import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm';
|
|
||||||
|
|
||||||
@Entity('smart_info', { synchronize: false })
|
|
||||||
export class SmartInfoEntity {
|
|
||||||
@OneToOne(() => AssetEntity, { onDelete: 'CASCADE', nullable: true })
|
|
||||||
@JoinColumn({ name: 'assetId', referencedColumnName: 'id' })
|
|
||||||
asset?: AssetEntity;
|
|
||||||
|
|
||||||
@PrimaryColumn()
|
|
||||||
assetId!: string;
|
|
||||||
|
|
||||||
@Column({ type: 'text', array: true, nullable: true })
|
|
||||||
tags!: string[] | null;
|
|
||||||
|
|
||||||
@Column({ type: 'text', array: true, nullable: true })
|
|
||||||
objects!: string[] | null;
|
|
||||||
}
|
|
|
@ -28,9 +28,7 @@ export enum WithoutProperty {
|
||||||
EXIF = 'exif',
|
EXIF = 'exif',
|
||||||
SMART_SEARCH = 'smart-search',
|
SMART_SEARCH = 'smart-search',
|
||||||
DUPLICATE = 'duplicate',
|
DUPLICATE = 'duplicate',
|
||||||
OBJECT_TAGS = 'object-tags',
|
|
||||||
FACES = 'faces',
|
FACES = 'faces',
|
||||||
PERSON = 'person',
|
|
||||||
SIDECAR = 'sidecar',
|
SIDECAR = 'sidecar',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +92,6 @@ export type AssetWithoutRelations = Omit<
|
||||||
| 'library'
|
| 'library'
|
||||||
| 'exifInfo'
|
| 'exifInfo'
|
||||||
| 'sharedLinks'
|
| 'sharedLinks'
|
||||||
| 'smartInfo'
|
|
||||||
| 'smartSearch'
|
| 'smartSearch'
|
||||||
| 'tags'
|
| 'tags'
|
||||||
>;
|
>;
|
||||||
|
@ -190,7 +187,6 @@ export interface IAssetRepository {
|
||||||
upsertExif(exif: Partial<ExifEntity>): Promise<void>;
|
upsertExif(exif: Partial<ExifEntity>): Promise<void>;
|
||||||
upsertJobStatus(...jobStatus: Partial<AssetJobStatusEntity>[]): Promise<void>;
|
upsertJobStatus(...jobStatus: Partial<AssetJobStatusEntity>[]): Promise<void>;
|
||||||
getAssetIdByCity(userId: string, options: AssetExploreFieldOptions): Promise<SearchExploreItem<string>>;
|
getAssetIdByCity(userId: string, options: AssetExploreFieldOptions): Promise<SearchExploreItem<string>>;
|
||||||
getAssetIdByTag(userId: string, options: AssetExploreFieldOptions): Promise<SearchExploreItem<string>>;
|
|
||||||
getDuplicates(options: AssetBuilderOptions): Promise<AssetEntity[]>;
|
getDuplicates(options: AssetBuilderOptions): Promise<AssetEntity[]>;
|
||||||
getAllForUserFullSync(options: AssetFullSyncOptions): Promise<AssetEntity[]>;
|
getAllForUserFullSync(options: AssetFullSyncOptions): Promise<AssetEntity[]>;
|
||||||
getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise<AssetEntity[]>;
|
getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise<AssetEntity[]>;
|
||||||
|
|
|
@ -68,7 +68,6 @@ export interface SearchStatusOptions {
|
||||||
|
|
||||||
export interface SearchOneToOneRelationOptions {
|
export interface SearchOneToOneRelationOptions {
|
||||||
withExif?: boolean;
|
withExif?: boolean;
|
||||||
withSmartInfo?: boolean;
|
|
||||||
withStacked?: boolean;
|
withStacked?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
server/src/migrations/1730989238718-DropSmartInfoTable.ts
Normal file
11
server/src/migrations/1730989238718-DropSmartInfoTable.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class DropSmartInfoTable1730989238718 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE smart_info`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(): Promise<void> {
|
||||||
|
// not implemented
|
||||||
|
}
|
||||||
|
}
|
|
@ -183,9 +183,6 @@ SELECT
|
||||||
"AssetEntity__AssetEntity_exifInfo"."bitsPerSample" AS "AssetEntity__AssetEntity_exifInfo_bitsPerSample",
|
"AssetEntity__AssetEntity_exifInfo"."bitsPerSample" AS "AssetEntity__AssetEntity_exifInfo_bitsPerSample",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."rating" AS "AssetEntity__AssetEntity_exifInfo_rating",
|
"AssetEntity__AssetEntity_exifInfo"."rating" AS "AssetEntity__AssetEntity_exifInfo_rating",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."fps" AS "AssetEntity__AssetEntity_exifInfo_fps",
|
"AssetEntity__AssetEntity_exifInfo"."fps" AS "AssetEntity__AssetEntity_exifInfo_fps",
|
||||||
"AssetEntity__AssetEntity_smartInfo"."assetId" AS "AssetEntity__AssetEntity_smartInfo_assetId",
|
|
||||||
"AssetEntity__AssetEntity_smartInfo"."tags" AS "AssetEntity__AssetEntity_smartInfo_tags",
|
|
||||||
"AssetEntity__AssetEntity_smartInfo"."objects" AS "AssetEntity__AssetEntity_smartInfo_objects",
|
|
||||||
"AssetEntity__AssetEntity_tags"."id" AS "AssetEntity__AssetEntity_tags_id",
|
"AssetEntity__AssetEntity_tags"."id" AS "AssetEntity__AssetEntity_tags_id",
|
||||||
"AssetEntity__AssetEntity_tags"."value" AS "AssetEntity__AssetEntity_tags_value",
|
"AssetEntity__AssetEntity_tags"."value" AS "AssetEntity__AssetEntity_tags_value",
|
||||||
"AssetEntity__AssetEntity_tags"."createdAt" AS "AssetEntity__AssetEntity_tags_createdAt",
|
"AssetEntity__AssetEntity_tags"."createdAt" AS "AssetEntity__AssetEntity_tags_createdAt",
|
||||||
|
@ -252,7 +249,6 @@ SELECT
|
||||||
FROM
|
FROM
|
||||||
"assets" "AssetEntity"
|
"assets" "AssetEntity"
|
||||||
LEFT JOIN "exif" "AssetEntity__AssetEntity_exifInfo" ON "AssetEntity__AssetEntity_exifInfo"."assetId" = "AssetEntity"."id"
|
LEFT JOIN "exif" "AssetEntity__AssetEntity_exifInfo" ON "AssetEntity__AssetEntity_exifInfo"."assetId" = "AssetEntity"."id"
|
||||||
LEFT JOIN "smart_info" "AssetEntity__AssetEntity_smartInfo" ON "AssetEntity__AssetEntity_smartInfo"."assetId" = "AssetEntity"."id"
|
|
||||||
LEFT JOIN "tag_asset" "AssetEntity_AssetEntity__AssetEntity_tags" ON "AssetEntity_AssetEntity__AssetEntity_tags"."assetsId" = "AssetEntity"."id"
|
LEFT JOIN "tag_asset" "AssetEntity_AssetEntity__AssetEntity_tags" ON "AssetEntity_AssetEntity__AssetEntity_tags"."assetsId" = "AssetEntity"."id"
|
||||||
LEFT JOIN "tags" "AssetEntity__AssetEntity_tags" ON "AssetEntity__AssetEntity_tags"."id" = "AssetEntity_AssetEntity__AssetEntity_tags"."tagsId"
|
LEFT JOIN "tags" "AssetEntity__AssetEntity_tags" ON "AssetEntity__AssetEntity_tags"."id" = "AssetEntity_AssetEntity__AssetEntity_tags"."tagsId"
|
||||||
LEFT JOIN "asset_faces" "AssetEntity__AssetEntity_faces" ON "AssetEntity__AssetEntity_faces"."assetId" = "AssetEntity"."id"
|
LEFT JOIN "asset_faces" "AssetEntity__AssetEntity_faces" ON "AssetEntity__AssetEntity_faces"."assetId" = "AssetEntity"."id"
|
||||||
|
@ -932,36 +928,6 @@ WHERE
|
||||||
LIMIT
|
LIMIT
|
||||||
12
|
12
|
||||||
|
|
||||||
-- AssetRepository.getAssetIdByTag
|
|
||||||
WITH
|
|
||||||
"random_tags" AS (
|
|
||||||
SELECT
|
|
||||||
unnest(tags) AS "tag"
|
|
||||||
FROM
|
|
||||||
"smart_info" "si"
|
|
||||||
GROUP BY
|
|
||||||
tag
|
|
||||||
HAVING
|
|
||||||
count(*) >= $1
|
|
||||||
)
|
|
||||||
SELECT DISTINCT
|
|
||||||
ON (unnest("si"."tags")) "asset"."id" AS "data",
|
|
||||||
unnest("si"."tags") AS "value"
|
|
||||||
FROM
|
|
||||||
"assets" "asset"
|
|
||||||
INNER JOIN "smart_info" "si" ON "asset"."id" = si."assetId"
|
|
||||||
INNER JOIN "random_tags" "t" ON "si"."tags" @> ARRAY[t.tag]
|
|
||||||
WHERE
|
|
||||||
(
|
|
||||||
"asset"."isVisible" = true
|
|
||||||
AND "asset"."type" = $2
|
|
||||||
AND "asset"."ownerId" IN ($3)
|
|
||||||
AND "asset"."isArchived" = $4
|
|
||||||
)
|
|
||||||
AND ("asset"."deletedAt" IS NULL)
|
|
||||||
LIMIT
|
|
||||||
12
|
|
||||||
|
|
||||||
-- AssetRepository.getAllForUserFullSync
|
-- AssetRepository.getAllForUserFullSync
|
||||||
SELECT
|
SELECT
|
||||||
"asset"."id" AS "asset_id",
|
"asset"."id" AS "asset_id",
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||||
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
|
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { ExifEntity } from 'src/entities/exif.entity';
|
import { ExifEntity } from 'src/entities/exif.entity';
|
||||||
import { SmartInfoEntity } from 'src/entities/smart-info.entity';
|
|
||||||
import { AssetFileType, AssetOrder, AssetStatus, AssetType, PaginationMode } from 'src/enum';
|
import { AssetFileType, AssetOrder, AssetStatus, AssetType, PaginationMode } from 'src/enum';
|
||||||
import {
|
import {
|
||||||
AssetBuilderOptions,
|
AssetBuilderOptions,
|
||||||
|
@ -60,7 +59,6 @@ export class AssetRepository implements IAssetRepository {
|
||||||
@InjectRepository(AssetFileEntity) private fileRepository: Repository<AssetFileEntity>,
|
@InjectRepository(AssetFileEntity) private fileRepository: Repository<AssetFileEntity>,
|
||||||
@InjectRepository(ExifEntity) private exifRepository: Repository<ExifEntity>,
|
@InjectRepository(ExifEntity) private exifRepository: Repository<ExifEntity>,
|
||||||
@InjectRepository(AssetJobStatusEntity) private jobStatusRepository: Repository<AssetJobStatusEntity>,
|
@InjectRepository(AssetJobStatusEntity) private jobStatusRepository: Repository<AssetJobStatusEntity>,
|
||||||
@InjectRepository(SmartInfoEntity) private smartInfoRepository: Repository<SmartInfoEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async upsertExif(exif: Partial<ExifEntity>): Promise<void> {
|
async upsertExif(exif: Partial<ExifEntity>): Promise<void> {
|
||||||
|
@ -119,7 +117,6 @@ export class AssetRepository implements IAssetRepository {
|
||||||
where: { id: In(ids) },
|
where: { id: In(ids) },
|
||||||
relations: {
|
relations: {
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
smartInfo: true,
|
|
||||||
tags: true,
|
tags: true,
|
||||||
faces: {
|
faces: {
|
||||||
person: true,
|
person: true,
|
||||||
|
@ -422,22 +419,6 @@ export class AssetRepository implements IAssetRepository {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case WithoutProperty.OBJECT_TAGS: {
|
|
||||||
relations = {
|
|
||||||
smartInfo: true,
|
|
||||||
};
|
|
||||||
where = {
|
|
||||||
jobStatus: {
|
|
||||||
previewAt: Not(IsNull()),
|
|
||||||
},
|
|
||||||
isVisible: true,
|
|
||||||
smartInfo: {
|
|
||||||
tags: IsNull(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case WithoutProperty.FACES: {
|
case WithoutProperty.FACES: {
|
||||||
relations = {
|
relations = {
|
||||||
faces: true,
|
faces: true,
|
||||||
|
@ -457,23 +438,6 @@ export class AssetRepository implements IAssetRepository {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case WithoutProperty.PERSON: {
|
|
||||||
relations = {
|
|
||||||
faces: true,
|
|
||||||
};
|
|
||||||
where = {
|
|
||||||
jobStatus: {
|
|
||||||
previewAt: Not(IsNull()),
|
|
||||||
},
|
|
||||||
isVisible: true,
|
|
||||||
faces: {
|
|
||||||
assetId: Not(IsNull()),
|
|
||||||
personId: IsNull(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case WithoutProperty.SIDECAR: {
|
case WithoutProperty.SIDECAR: {
|
||||||
where = [
|
where = [
|
||||||
{ sidecarPath: IsNull(), isVisible: true },
|
{ sidecarPath: IsNull(), isVisible: true },
|
||||||
|
@ -611,35 +575,6 @@ export class AssetRepository implements IAssetRepository {
|
||||||
return { fieldName: 'exifInfo.city', items };
|
return { fieldName: 'exifInfo.city', items };
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID, { minAssetsPerField: 5, maxFields: 12 }] })
|
|
||||||
async getAssetIdByTag(
|
|
||||||
ownerId: string,
|
|
||||||
{ minAssetsPerField, maxFields }: AssetExploreFieldOptions,
|
|
||||||
): Promise<SearchExploreItem<string>> {
|
|
||||||
const cte = this.smartInfoRepository
|
|
||||||
.createQueryBuilder('si')
|
|
||||||
.select('unnest(tags)', 'tag')
|
|
||||||
.groupBy('tag')
|
|
||||||
.having('count(*) >= :minAssetsPerField', { minAssetsPerField });
|
|
||||||
|
|
||||||
const items = await this.getBuilder({
|
|
||||||
userIds: [ownerId],
|
|
||||||
exifInfo: false,
|
|
||||||
assetType: AssetType.IMAGE,
|
|
||||||
isArchived: false,
|
|
||||||
})
|
|
||||||
.select('unnest(si.tags)', 'value')
|
|
||||||
.addSelect('asset.id', 'data')
|
|
||||||
.distinctOn(['unnest(si.tags)'])
|
|
||||||
.innerJoin('smart_info', 'si', 'asset.id = si."assetId"')
|
|
||||||
.addCommonTableExpression(cte, 'random_tags')
|
|
||||||
.innerJoin('random_tags', 't', 'si.tags @> ARRAY[t.tag]')
|
|
||||||
.limit(maxFields)
|
|
||||||
.getRawMany();
|
|
||||||
|
|
||||||
return { fieldName: 'smartInfo.tags', items };
|
|
||||||
}
|
|
||||||
|
|
||||||
private getBuilder(options: AssetBuilderOptions) {
|
private getBuilder(options: AssetBuilderOptions) {
|
||||||
const builder = this.repository.createQueryBuilder('asset').where('asset.isVisible = true');
|
const builder = this.repository.createQueryBuilder('asset').where('asset.isVisible = true');
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { ExifEntity } from 'src/entities/exif.entity';
|
import { ExifEntity } from 'src/entities/exif.entity';
|
||||||
import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
|
import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
|
||||||
import { SmartInfoEntity } from 'src/entities/smart-info.entity';
|
|
||||||
import { SmartSearchEntity } from 'src/entities/smart-search.entity';
|
import { SmartSearchEntity } from 'src/entities/smart-search.entity';
|
||||||
import { AssetType, PaginationMode } from 'src/enum';
|
import { AssetType, PaginationMode } from 'src/enum';
|
||||||
import { IConfigRepository } from 'src/interfaces/config.interface';
|
import { IConfigRepository } from 'src/interfaces/config.interface';
|
||||||
|
@ -34,7 +33,6 @@ export class SearchRepository implements ISearchRepository {
|
||||||
private assetsByCityQuery: string;
|
private assetsByCityQuery: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(SmartInfoEntity) private repository: Repository<SmartInfoEntity>,
|
|
||||||
@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
|
@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
|
||||||
@InjectRepository(ExifEntity) private exifRepository: Repository<ExifEntity>,
|
@InjectRepository(ExifEntity) private exifRepository: Repository<ExifEntity>,
|
||||||
@InjectRepository(AssetFaceEntity) private assetFaceRepository: Repository<AssetFaceEntity>,
|
@InjectRepository(AssetFaceEntity) private assetFaceRepository: Repository<AssetFaceEntity>,
|
||||||
|
@ -278,7 +276,7 @@ export class SearchRepository implements ISearchRepository {
|
||||||
@GenerateSql({ params: [[DummyValue.UUID]] })
|
@GenerateSql({ params: [[DummyValue.UUID]] })
|
||||||
async getAssetsByCity(userIds: string[]): Promise<AssetEntity[]> {
|
async getAssetsByCity(userIds: string[]): Promise<AssetEntity[]> {
|
||||||
const parameters = [userIds, true, false, AssetType.IMAGE];
|
const parameters = [userIds, true, false, AssetType.IMAGE];
|
||||||
const rawRes = await this.repository.query(this.assetsByCityQuery, parameters);
|
const rawRes = await this.assetRepository.query(this.assetsByCityQuery, parameters);
|
||||||
|
|
||||||
const items: AssetEntity[] = [];
|
const items: AssetEntity[] = [];
|
||||||
for (const res of rawRes) {
|
for (const res of rawRes) {
|
||||||
|
|
|
@ -94,7 +94,6 @@ export class AssetService extends BaseService {
|
||||||
{
|
{
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
sharedLinks: true,
|
sharedLinks: true,
|
||||||
smartInfo: true,
|
|
||||||
tags: true,
|
tags: true,
|
||||||
owner: true,
|
owner: true,
|
||||||
faces: {
|
faces: {
|
||||||
|
@ -162,7 +161,6 @@ export class AssetService extends BaseService {
|
||||||
const asset = await this.assetRepository.getById(id, {
|
const asset = await this.assetRepository.getById(id, {
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
owner: true,
|
owner: true,
|
||||||
smartInfo: true,
|
|
||||||
tags: true,
|
tags: true,
|
||||||
faces: {
|
faces: {
|
||||||
person: true,
|
person: true,
|
||||||
|
|
|
@ -47,14 +47,9 @@ describe(SearchService.name, () => {
|
||||||
fieldName: 'exifInfo.city',
|
fieldName: 'exifInfo.city',
|
||||||
items: [{ value: 'Paris', data: assetStub.image.id }],
|
items: [{ value: 'Paris', data: assetStub.image.id }],
|
||||||
});
|
});
|
||||||
assetMock.getAssetIdByTag.mockResolvedValue({
|
|
||||||
fieldName: 'smartInfo.tags',
|
|
||||||
items: [{ value: 'train', data: assetStub.imageFrom2015.id }],
|
|
||||||
});
|
|
||||||
assetMock.getByIdsWithAllRelations.mockResolvedValue([assetStub.image, assetStub.imageFrom2015]);
|
assetMock.getByIdsWithAllRelations.mockResolvedValue([assetStub.image, assetStub.imageFrom2015]);
|
||||||
const expectedResponse = [
|
const expectedResponse = [
|
||||||
{ fieldName: 'exifInfo.city', items: [{ value: 'Paris', data: mapAsset(assetStub.image) }] },
|
{ fieldName: 'exifInfo.city', items: [{ value: 'Paris', data: mapAsset(assetStub.image) }] },
|
||||||
{ fieldName: 'smartInfo.tags', items: [{ value: 'train', data: mapAsset(assetStub.imageFrom2015) }] },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = await sut.getExploreData(authStub.user1);
|
const result = await sut.getExploreData(authStub.user1);
|
||||||
|
|
|
@ -34,10 +34,8 @@ export class SearchService extends BaseService {
|
||||||
|
|
||||||
async getExploreData(auth: AuthDto): Promise<SearchExploreItem<AssetResponseDto>[]> {
|
async getExploreData(auth: AuthDto): Promise<SearchExploreItem<AssetResponseDto>[]> {
|
||||||
const options = { maxFields: 12, minAssetsPerField: 5 };
|
const options = { maxFields: 12, minAssetsPerField: 5 };
|
||||||
const results = await Promise.all([
|
const result = await this.assetRepository.getAssetIdByCity(auth.user.id, options);
|
||||||
this.assetRepository.getAssetIdByCity(auth.user.id, options),
|
const results = [result];
|
||||||
this.assetRepository.getAssetIdByTag(auth.user.id, options),
|
|
||||||
]);
|
|
||||||
const assetIds = new Set<string>(results.flatMap((field) => field.items.map((item) => item.data)));
|
const assetIds = new Set<string>(results.flatMap((field) => field.items.map((item) => item.data)));
|
||||||
const assets = await this.assetRepository.getByIdsWithAllRelations([...assetIds]);
|
const assets = await this.assetRepository.getByIdsWithAllRelations([...assetIds]);
|
||||||
const assetMap = new Map<string, AssetResponseDto>(assets.map((asset) => [asset.id, mapAsset(asset)]));
|
const assetMap = new Map<string, AssetResponseDto>(assets.map((asset) => [asset.id, mapAsset(asset)]));
|
||||||
|
|
|
@ -90,7 +90,6 @@ export function searchAssetBuilder(
|
||||||
isNotInAlbum,
|
isNotInAlbum,
|
||||||
withFaces,
|
withFaces,
|
||||||
withPeople,
|
withPeople,
|
||||||
withSmartInfo,
|
|
||||||
personIds,
|
personIds,
|
||||||
withStacked,
|
withStacked,
|
||||||
trashedAfter,
|
trashedAfter,
|
||||||
|
@ -123,10 +122,6 @@ export function searchAssetBuilder(
|
||||||
builder.leftJoinAndSelect('faces.person', 'person');
|
builder.leftJoinAndSelect('faces.person', 'person');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (withSmartInfo) {
|
|
||||||
builder.leftJoinAndSelect(`${builder.alias}.smartInfo`, 'smartInfo');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (personIds && personIds.length > 0) {
|
if (personIds && personIds.length > 0) {
|
||||||
const cte = builder
|
const cte = builder
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
|
|
10
server/test/fixtures/shared-link.stub.ts
vendored
10
server/test/fixtures/shared-link.stub.ts
vendored
|
@ -62,10 +62,6 @@ const assetResponse: AssetResponseDto = {
|
||||||
updatedAt: today,
|
updatedAt: today,
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
isArchived: false,
|
isArchived: false,
|
||||||
smartInfo: {
|
|
||||||
tags: [],
|
|
||||||
objects: ['a', 'b', 'c'],
|
|
||||||
},
|
|
||||||
duration: '0:00:00.00000',
|
duration: '0:00:00.00000',
|
||||||
exifInfo: assetInfo,
|
exifInfo: assetInfo,
|
||||||
livePhotoVideoId: null,
|
livePhotoVideoId: null,
|
||||||
|
@ -205,12 +201,6 @@ export const sharedLinkStub = {
|
||||||
isArchived: false,
|
isArchived: false,
|
||||||
isExternal: false,
|
isExternal: false,
|
||||||
isOffline: false,
|
isOffline: false,
|
||||||
smartInfo: {
|
|
||||||
assetId: 'id_1',
|
|
||||||
tags: [],
|
|
||||||
objects: ['a', 'b', 'c'],
|
|
||||||
asset: null as any,
|
|
||||||
},
|
|
||||||
files: [],
|
files: [],
|
||||||
thumbhash: null,
|
thumbhash: null,
|
||||||
encodedVideoPath: '',
|
encodedVideoPath: '',
|
||||||
|
|
|
@ -33,7 +33,6 @@ export const newAssetRepositoryMock = (): Mocked<IAssetRepository> => {
|
||||||
getTimeBucket: vitest.fn(),
|
getTimeBucket: vitest.fn(),
|
||||||
getTimeBuckets: vitest.fn(),
|
getTimeBuckets: vitest.fn(),
|
||||||
getAssetIdByCity: vitest.fn(),
|
getAssetIdByCity: vitest.fn(),
|
||||||
getAssetIdByTag: vitest.fn(),
|
|
||||||
getAllForUserFullSync: vitest.fn(),
|
getAllForUserFullSync: vitest.fn(),
|
||||||
getChangedDeltaSync: vitest.fn(),
|
getChangedDeltaSync: vitest.fn(),
|
||||||
getDuplicates: vitest.fn(),
|
getDuplicates: vitest.fn(),
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
|
|
||||||
enum Field {
|
enum Field {
|
||||||
CITY = 'exifInfo.city',
|
CITY = 'exifInfo.city',
|
||||||
TAGS = 'smartInfo.tags',
|
|
||||||
OBJECTS = 'smartInfo.objects',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFieldItems = (items: SearchExploreResponseDto[], field: Field) => {
|
const getFieldItems = (items: SearchExploreResponseDto[], field: Field) => {
|
||||||
|
|
Loading…
Reference in a new issue