feat: asset metadata (#20446)

This commit is contained in:
Jason Rasmussen
2025-08-27 14:31:23 -04:00
committed by GitHub
parent 25a94bd117
commit 88072910da
37 changed files with 1999 additions and 21 deletions

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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();
}