mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
refactor: remove tag entity (#17462)
This commit is contained in:
parent
75bc32b47b
commit
b6c5a03533
@ -92,6 +92,13 @@ export type Asset = {
|
||||
type: AssetType;
|
||||
};
|
||||
|
||||
export type SidecarWriteAsset = {
|
||||
id: string;
|
||||
sidecarPath: string | null;
|
||||
originalPath: string;
|
||||
tags: Array<{ value: string }>;
|
||||
};
|
||||
|
||||
export type AuthSharedLink = {
|
||||
id: string;
|
||||
expiresAt: Date | null;
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsHexColor, IsNotEmpty, IsString } from 'class-validator';
|
||||
import { TagEntity } from 'src/entities/tag.entity';
|
||||
import { TagItem } from 'src/types';
|
||||
import { Optional, ValidateHexColor, ValidateUUID } from 'src/validation';
|
||||
|
||||
@ -52,7 +51,7 @@ export class TagResponseDto {
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export function mapTag(entity: TagItem | TagEntity): TagResponseDto {
|
||||
export function mapTag(entity: TagItem): TagResponseDto {
|
||||
return {
|
||||
id: entity.id,
|
||||
parentId: entity.parentId ?? undefined,
|
||||
|
@ -8,11 +8,11 @@ import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
|
||||
import { ExifEntity } from 'src/entities/exif.entity';
|
||||
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||
import { StackEntity } from 'src/entities/stack.entity';
|
||||
import { TagEntity } from 'src/entities/tag.entity';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { AssetFileType, AssetStatus, AssetType } from 'src/enum';
|
||||
import { TimeBucketSize } from 'src/repositories/asset.repository';
|
||||
import { AssetSearchBuilderOptions } from 'src/repositories/search.repository';
|
||||
import { TagItem } from 'src/types';
|
||||
import { anyUuid, asUuid } from 'src/utils/database';
|
||||
|
||||
export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_checksum';
|
||||
@ -49,7 +49,7 @@ export class AssetEntity {
|
||||
originalFileName!: string;
|
||||
sidecarPath!: string | null;
|
||||
exifInfo?: ExifEntity;
|
||||
tags!: TagEntity[];
|
||||
tags?: TagItem[];
|
||||
sharedLinks!: SharedLinkEntity[];
|
||||
albums?: AlbumEntity[];
|
||||
faces!: AssetFaceEntity[];
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
|
||||
export class TagEntity {
|
||||
id!: string;
|
||||
value!: string;
|
||||
createdAt!: Date;
|
||||
updatedAt!: Date;
|
||||
updateId?: string;
|
||||
color!: string | null;
|
||||
parentId?: string;
|
||||
parent?: TagEntity;
|
||||
children?: TagEntity[];
|
||||
user?: UserEntity;
|
||||
userId!: string;
|
||||
assets?: AssetEntity[];
|
||||
}
|
@ -210,6 +210,32 @@ where
|
||||
limit
|
||||
$3
|
||||
|
||||
-- AssetRepository.getAssetForSidecarWriteJob
|
||||
select
|
||||
"id",
|
||||
"sidecarPath",
|
||||
"originalPath",
|
||||
(
|
||||
select
|
||||
coalesce(json_agg(agg), '[]')
|
||||
from
|
||||
(
|
||||
select
|
||||
"tags"."value"
|
||||
from
|
||||
"tags"
|
||||
inner join "tag_asset" on "tags"."id" = "tag_asset"."tagsId"
|
||||
where
|
||||
"assets"."id" = "tag_asset"."assetsId"
|
||||
) as agg
|
||||
) as "tags"
|
||||
from
|
||||
"assets"
|
||||
where
|
||||
"assets"."id" = $1::uuid
|
||||
limit
|
||||
$2
|
||||
|
||||
-- AssetRepository.getById
|
||||
select
|
||||
"assets".*
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Insertable, Kysely, Selectable, UpdateResult, Updateable, sql } from 'kysely';
|
||||
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
||||
import { isEmpty, isUndefined, omitBy } from 'lodash';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { AssetFiles, AssetJobStatus, Assets, DB, Exif } from 'src/db';
|
||||
@ -495,6 +496,27 @@ export class AssetRepository {
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getAssetForSidecarWriteJob(id: string) {
|
||||
return this.db
|
||||
.selectFrom('assets')
|
||||
.where('assets.id', '=', asUuid(id))
|
||||
.select((eb) => [
|
||||
'id',
|
||||
'sidecarPath',
|
||||
'originalPath',
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom('tags')
|
||||
.select(['tags.value'])
|
||||
.innerJoin('tag_asset', 'tags.id', 'tag_asset.tagsId')
|
||||
.whereRef('assets.id', '=', 'tag_asset.assetsId'),
|
||||
).as('tags'),
|
||||
])
|
||||
.limit(1)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getById(
|
||||
id: string,
|
||||
|
@ -15,6 +15,7 @@ import { probeStub } from 'test/fixtures/media.stub';
|
||||
import { metadataStub } from 'test/fixtures/metadata.stub';
|
||||
import { personStub } from 'test/fixtures/person.stub';
|
||||
import { tagStub } from 'test/fixtures/tag.stub';
|
||||
import { factory } from 'test/small.factory';
|
||||
import { newTestService, ServiceMocks } from 'test/utils';
|
||||
|
||||
describe(MetadataService.name, () => {
|
||||
@ -1405,33 +1406,35 @@ describe(MetadataService.name, () => {
|
||||
|
||||
describe('handleSidecarWrite', () => {
|
||||
it('should skip assets that do not exist anymore', async () => {
|
||||
mocks.asset.getByIds.mockResolvedValue([]);
|
||||
mocks.asset.getAssetForSidecarWriteJob.mockResolvedValue(void 0);
|
||||
await expect(sut.handleSidecarWrite({ id: 'asset-123' })).resolves.toBe(JobStatus.FAILED);
|
||||
expect(mocks.metadata.writeTags).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should skip jobs with not metadata', async () => {
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||
await expect(sut.handleSidecarWrite({ id: assetStub.sidecar.id })).resolves.toBe(JobStatus.SKIPPED);
|
||||
it('should skip jobs with no metadata', async () => {
|
||||
const asset = factory.jobAssets.sidecarWrite();
|
||||
mocks.asset.getAssetForSidecarWriteJob.mockResolvedValue(asset);
|
||||
await expect(sut.handleSidecarWrite({ id: asset.id })).resolves.toBe(JobStatus.SKIPPED);
|
||||
expect(mocks.metadata.writeTags).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should write tags', async () => {
|
||||
const asset = factory.jobAssets.sidecarWrite();
|
||||
const description = 'this is a description';
|
||||
const gps = 12;
|
||||
const date = '2023-11-22T04:56:12.196Z';
|
||||
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||
mocks.asset.getAssetForSidecarWriteJob.mockResolvedValue(asset);
|
||||
await expect(
|
||||
sut.handleSidecarWrite({
|
||||
id: assetStub.sidecar.id,
|
||||
id: asset.id,
|
||||
description,
|
||||
latitude: gps,
|
||||
longitude: gps,
|
||||
dateTimeOriginal: date,
|
||||
}),
|
||||
).resolves.toBe(JobStatus.SUCCESS);
|
||||
expect(mocks.metadata.writeTags).toHaveBeenCalledWith(assetStub.sidecar.sidecarPath, {
|
||||
expect(mocks.metadata.writeTags).toHaveBeenCalledWith(asset.sidecarPath, {
|
||||
Description: description,
|
||||
ImageDescription: description,
|
||||
DateTimeOriginal: date,
|
||||
|
@ -316,7 +316,7 @@ export class MetadataService extends BaseService {
|
||||
@OnJob({ name: JobName.SIDECAR_WRITE, queue: QueueName.SIDECAR })
|
||||
async handleSidecarWrite(job: JobOf<JobName.SIDECAR_WRITE>): Promise<JobStatus> {
|
||||
const { id, description, dateTimeOriginal, latitude, longitude, rating, tags } = job;
|
||||
const [asset] = await this.assetRepository.getByIds([id], { tags: true });
|
||||
const asset = await this.assetRepository.getAssetForSidecarWriteJob(id);
|
||||
if (!asset) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
19
server/test/fixtures/asset.stub.ts
vendored
19
server/test/fixtures/asset.stub.ts
vendored
@ -89,7 +89,6 @@ export const assetStub = {
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
faces: [],
|
||||
sidecarPath: null,
|
||||
@ -123,7 +122,6 @@ export const assetStub = {
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'IMG_456.jpg',
|
||||
faces: [],
|
||||
@ -162,7 +160,6 @@ export const assetStub = {
|
||||
isExternal: false,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.ext',
|
||||
faces: [],
|
||||
@ -197,7 +194,6 @@ export const assetStub = {
|
||||
isExternal: false,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.jpg',
|
||||
faces: [],
|
||||
@ -243,7 +239,6 @@ export const assetStub = {
|
||||
isExternal: false,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.jpg',
|
||||
faces: [],
|
||||
@ -283,7 +278,6 @@ export const assetStub = {
|
||||
isExternal: false,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.jpg',
|
||||
faces: [],
|
||||
@ -325,7 +319,6 @@ export const assetStub = {
|
||||
isExternal: false,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.jpg',
|
||||
faces: [],
|
||||
@ -363,7 +356,6 @@ export const assetStub = {
|
||||
isExternal: false,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.jpg',
|
||||
faces: [],
|
||||
@ -404,7 +396,6 @@ export const assetStub = {
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
libraryId: 'library-id',
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.jpg',
|
||||
faces: [],
|
||||
@ -443,7 +434,6 @@ export const assetStub = {
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
isExternal: false,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.ext',
|
||||
faces: [],
|
||||
@ -480,7 +470,6 @@ export const assetStub = {
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.ext',
|
||||
faces: [],
|
||||
@ -519,7 +508,6 @@ export const assetStub = {
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
faces: [],
|
||||
sidecarPath: null,
|
||||
@ -608,7 +596,6 @@ export const assetStub = {
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.ext',
|
||||
faces: [],
|
||||
@ -650,7 +637,6 @@ export const assetStub = {
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.ext',
|
||||
faces: [],
|
||||
@ -685,7 +671,6 @@ export const assetStub = {
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.ext',
|
||||
faces: [],
|
||||
@ -721,7 +706,6 @@ export const assetStub = {
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
faces: [],
|
||||
sidecarPath: null,
|
||||
@ -759,7 +743,6 @@ export const assetStub = {
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
libraryId: 'library-id',
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'photo.jpg',
|
||||
faces: [],
|
||||
@ -797,7 +780,6 @@ export const assetStub = {
|
||||
isExternal: false,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.dng',
|
||||
faces: [],
|
||||
@ -837,7 +819,6 @@ export const assetStub = {
|
||||
isExternal: false,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.hif',
|
||||
faces: [],
|
||||
|
1
server/test/fixtures/shared-link.stub.ts
vendored
1
server/test/fixtures/shared-link.stub.ts
vendored
@ -241,7 +241,6 @@ export const sharedLinkStub = {
|
||||
autoStackId: null,
|
||||
rating: 3,
|
||||
},
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
faces: [],
|
||||
sidecarPath: null,
|
||||
|
@ -12,6 +12,7 @@ export const newAssetRepositoryMock = (): Mocked<RepositoryInterface<AssetReposi
|
||||
getByDayOfYear: vitest.fn(),
|
||||
getByIds: vitest.fn().mockResolvedValue([]),
|
||||
getAssetForSearchDuplicatesJob: vitest.fn(),
|
||||
getAssetForSidecarWriteJob: vitest.fn(),
|
||||
getByIdsWithAllRelations: vitest.fn().mockResolvedValue([]),
|
||||
getByAlbumId: vitest.fn(),
|
||||
getByDeviceIds: vitest.fn(),
|
||||
|
@ -1,5 +1,15 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { ApiKey, Asset, AuthApiKey, AuthUser, Library, Partner, User, UserAdmin } from 'src/database';
|
||||
import {
|
||||
ApiKey,
|
||||
Asset,
|
||||
AuthApiKey,
|
||||
AuthUser,
|
||||
Library,
|
||||
Partner,
|
||||
SidecarWriteAsset,
|
||||
User,
|
||||
UserAdmin,
|
||||
} from 'src/database';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { AssetStatus, AssetType, MemoryType, Permission, UserStatus } from 'src/enum';
|
||||
import { ActivityItem, MemoryItem, OnThisDayData } from 'src/types';
|
||||
@ -210,6 +220,14 @@ const versionHistoryFactory = () => ({
|
||||
version: '1.123.45',
|
||||
});
|
||||
|
||||
const assetSidecarWriteFactory = (asset: Partial<SidecarWriteAsset> = {}) => ({
|
||||
id: newUuid(),
|
||||
sidecarPath: '/path/to/original-path.jpg.xmp',
|
||||
originalPath: '/path/to/original-path.jpg.xmp',
|
||||
tags: [],
|
||||
...asset,
|
||||
});
|
||||
|
||||
export const factory = {
|
||||
activity: activityFactory,
|
||||
apiKey: apiKeyFactory,
|
||||
@ -225,4 +243,7 @@ export const factory = {
|
||||
user: userFactory,
|
||||
userAdmin: userAdminFactory,
|
||||
versionHistory: versionHistoryFactory,
|
||||
jobAssets: {
|
||||
sidecarWrite: assetSidecarWriteFactory,
|
||||
},
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user