From fe55aad798ddb559b7201d18eb779acaea54756d Mon Sep 17 00:00:00 2001 From: sdjdd Date: Sun, 7 Apr 2024 13:21:08 +0800 Subject: [PATCH] feat(next/api): has one through pointer relation --- next/api/src/model/Ticket.ts | 6 ++--- next/api/src/orm/helpers.ts | 19 +++++++++++++++ next/api/src/orm/preloader.ts | 41 +++++++++++++++++++++++++++++++++ next/api/src/orm/relation.ts | 13 ++++++++++- next/api/src/response/ticket.ts | 3 +-- next/api/src/router/ticket.ts | 2 +- 6 files changed, 77 insertions(+), 7 deletions(-) diff --git a/next/api/src/model/Ticket.ts b/next/api/src/model/Ticket.ts index 0a46ee3bb..4e051f3f6 100644 --- a/next/api/src/model/Ticket.ts +++ b/next/api/src/model/Ticket.ts @@ -10,8 +10,8 @@ import { pointerId, pointerIds, pointTo, - hasManyThroughPointer, hasManyThroughPointerArray, + hasOneThroughPointer, serialize, } from '@/orm'; import { TicketUpdater, UpdateOptions } from '@/ticket/TicketUpdater'; @@ -210,8 +210,8 @@ export class Ticket extends Model { @field() privateTags?: Tag[]; - @hasManyThroughPointer(() => Notification) - notifications?: Notification[]; + @hasOneThroughPointer(() => Notification) + notification?: Notification; @pointerId(() => Ticket) parentId?: string; diff --git a/next/api/src/orm/helpers.ts b/next/api/src/orm/helpers.ts index dc8ad6736..da5c631f9 100644 --- a/next/api/src/orm/helpers.ts +++ b/next/api/src/orm/helpers.ts @@ -168,6 +168,25 @@ export function hasManyThroughRelation(getRelatedModel: ModelGetter, relatedKey? }; } +export function hasOneThroughPointer( + getRelatedModel: ModelGetter, + foreignPointerKey?: string, + foreignKeyField?: string +) { + return (target: Model, field: string) => { + const model = target.constructor as typeof Model; + foreignPointerKey ??= lowercaseFirstChar(model.getClassName()); + model.setRelation({ + type: RelationType.HasOneThroughPointer, + model, + field, + getRelatedModel, + foreignPointerKey, + foreignKeyField: foreignKeyField ?? foreignPointerKey + 'Id', + }); + }; +} + function serializeDecoratorFactory(config?: Partial>) { return (target: Model, key: string) => { const model = target.constructor as typeof Model; diff --git a/next/api/src/orm/preloader.ts b/next/api/src/orm/preloader.ts index 7d77af680..128fa0171 100644 --- a/next/api/src/orm/preloader.ts +++ b/next/api/src/orm/preloader.ts @@ -10,6 +10,7 @@ import { HasManyThroughPointer, HasManyThroughPointerArray, HasManyThroughRelation, + HasOneThroughPointer, RelationName, RelationType, } from './relation'; @@ -258,6 +259,44 @@ class HasManyThroughRelationPreloader { } } +class HasOneThroughPointerPreloader implements Preloader { + queryModifier?: (query: QueryBuilder) => void; + + constructor(private relation: HasOneThroughPointer) {} + + async load(items: Item[], options?: AuthOptions) { + if (items.length === 0) { + return; + } + + const { model, field, getRelatedModel, foreignPointerKey, foreignKeyField } = this.relation; + + const itemsChunks = _(items) + .uniqBy((item) => item.id) + .chunk(50) + .value(); + + const relatedItemsChunks: Item[][] = []; + + for (const items of itemsChunks) { + const query = getRelatedModel().queryBuilder(); + this.queryModifier?.(query); + const pointers = items.map((item) => model.ptr(item.id)); + query.where(foreignPointerKey, 'in', pointers); + const relatedItems = await query.find(options); + relatedItemsChunks.push(relatedItems); + } + + const relatedItemsGroups = _(relatedItemsChunks).flatten().groupBy(foreignKeyField).value(); + for (const item of items) { + const relatedItems = relatedItemsGroups[item.id]; + if (relatedItems) { + item[field] = relatedItems[0]; + } + } + } +} + export function preloaderFactory>( model: M, name: N @@ -281,5 +320,7 @@ export function preloaderFactory { return query.where('user', '==', currentUser.toPointer()); },