mirror of
https://github.com/immich-app/immich.git
synced 2026-03-01 10:08:42 +03:00
feat: asset metadata (#20446)
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Insertable, Kysely, NotNull, Selectable, UpdateResult, Updateable, sql } from 'kysely';
|
||||
import { Insertable, Kysely, NotNull, Selectable, sql, Updateable, UpdateResult } from 'kysely';
|
||||
import { isEmpty, isUndefined, omitBy } from 'lodash';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { Stack } from 'src/database';
|
||||
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { AssetFileType, AssetOrder, AssetStatus, AssetType, AssetVisibility } from 'src/enum';
|
||||
import { AssetFileType, AssetMetadataKey, AssetOrder, AssetStatus, AssetType, AssetVisibility } from 'src/enum';
|
||||
import { DB } from 'src/schema';
|
||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||
import { AssetFileTable } from 'src/schema/tables/asset-file.table';
|
||||
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { AssetMetadataItem } from 'src/types';
|
||||
import {
|
||||
anyUuid,
|
||||
asUuid,
|
||||
@@ -210,6 +211,43 @@ export class AssetRepository {
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getMetadata(assetId: string) {
|
||||
return this.db
|
||||
.selectFrom('asset_metadata')
|
||||
.select(['key', 'value', 'updatedAt'])
|
||||
.where('assetId', '=', assetId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
upsertMetadata(id: string, items: AssetMetadataItem[]) {
|
||||
return this.db
|
||||
.insertInto('asset_metadata')
|
||||
.values(items.map((item) => ({ assetId: id, ...item })))
|
||||
.onConflict((oc) =>
|
||||
oc
|
||||
.columns(['assetId', 'key'])
|
||||
.doUpdateSet((eb) => ({ key: eb.ref('excluded.key'), value: eb.ref('excluded.value') })),
|
||||
)
|
||||
.returning(['key', 'value', 'updatedAt'])
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
|
||||
getMetadataByKey(assetId: string, key: AssetMetadataKey) {
|
||||
return this.db
|
||||
.selectFrom('asset_metadata')
|
||||
.select(['key', 'value', 'updatedAt'])
|
||||
.where('assetId', '=', assetId)
|
||||
.where('key', '=', key)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
|
||||
async deleteMetadataByKey(id: string, key: AssetMetadataKey) {
|
||||
await this.db.deleteFrom('asset_metadata').where('assetId', '=', id).where('key', '=', key).execute();
|
||||
}
|
||||
|
||||
create(asset: Insertable<AssetTable>) {
|
||||
return this.db.insertInto('asset').values(asset).returningAll().executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ export class SyncRepository {
|
||||
asset: AssetSync;
|
||||
assetExif: AssetExifSync;
|
||||
assetFace: AssetFaceSync;
|
||||
assetMetadata: AssetMetadataSync;
|
||||
authUser: AuthUserSync;
|
||||
memory: MemorySync;
|
||||
memoryToAsset: MemoryToAssetSync;
|
||||
@@ -75,6 +76,7 @@ export class SyncRepository {
|
||||
this.asset = new AssetSync(this.db);
|
||||
this.assetExif = new AssetExifSync(this.db);
|
||||
this.assetFace = new AssetFaceSync(this.db);
|
||||
this.assetMetadata = new AssetMetadataSync(this.db);
|
||||
this.authUser = new AuthUserSync(this.db);
|
||||
this.memory = new MemorySync(this.db);
|
||||
this.memoryToAsset = new MemoryToAssetSync(this.db);
|
||||
@@ -685,3 +687,23 @@ class UserMetadataSync extends BaseSync {
|
||||
.stream();
|
||||
}
|
||||
}
|
||||
|
||||
class AssetMetadataSync extends BaseSync {
|
||||
@GenerateSql({ params: [dummyQueryOptions, DummyValue.UUID], stream: true })
|
||||
getDeletes(options: SyncQueryOptions, userId: string) {
|
||||
return this.auditQuery('asset_metadata_audit', options)
|
||||
.select(['asset_metadata_audit.id', 'assetId', 'key'])
|
||||
.leftJoin('asset', 'asset.id', 'asset_metadata_audit.assetId')
|
||||
.where('asset.ownerId', '=', userId)
|
||||
.stream();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [dummyQueryOptions, DummyValue.UUID], stream: true })
|
||||
getUpserts(options: SyncQueryOptions, userId: string) {
|
||||
return this.upsertQuery('asset_metadata', options)
|
||||
.select(['assetId', 'key', 'value', 'asset_metadata.updateId'])
|
||||
.innerJoin('asset', 'asset.id', 'asset_metadata.assetId')
|
||||
.where('asset.ownerId', '=', userId)
|
||||
.stream();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,10 @@ import { columns } from 'src/database';
|
||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { AssetType, AssetVisibility, UserStatus } from 'src/enum';
|
||||
import { DB } from 'src/schema';
|
||||
import { UserMetadataTable } from 'src/schema/tables/user-metadata.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import { UserMetadata, UserMetadataItem } from 'src/types';
|
||||
import { asUuid } from 'src/utils/database';
|
||||
|
||||
type Upsert = Insertable<UserMetadataTable>;
|
||||
|
||||
export interface UserListFilter {
|
||||
id?: string;
|
||||
withDeleted?: boolean;
|
||||
@@ -211,12 +208,12 @@ export class UserRepository {
|
||||
async upsertMetadata<T extends keyof UserMetadata>(id: string, { key, value }: { key: T; value: UserMetadata[T] }) {
|
||||
await this.db
|
||||
.insertInto('user_metadata')
|
||||
.values({ userId: id, key, value } as Upsert)
|
||||
.values({ userId: id, key, value })
|
||||
.onConflict((oc) =>
|
||||
oc.columns(['userId', 'key']).doUpdateSet({
|
||||
key,
|
||||
value,
|
||||
} as Upsert),
|
||||
}),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user