Files
huly-platform/server-plugins/card-resources/src/index.ts
T

858 lines
27 KiB
TypeScript

//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import card, { Card, MasterTag, Tag } from '@hcengineering/card'
import core, {
AccountUuid,
AnyAttribute,
ArrOf,
Class,
Data,
Doc,
DocumentUpdate,
fillDefaults,
generateId,
getDiffUpdate,
Mixin,
MixinUpdate,
notEmpty,
OperationDomain,
PersonId,
Ref,
RefTo,
Space,
splitMixinUpdate,
Tx,
TxCreateDoc,
TxMixin,
TxProcessor,
TxRemoveDoc,
TxUpdateDoc
} from '@hcengineering/core'
import { TriggerControl } from '@hcengineering/server-core'
import setting from '@hcengineering/setting'
import view from '@hcengineering/view'
import {
AddCollaboratorsEvent,
CardEventType,
NotificationEventType,
PeerEventType,
RemoveCardEvent,
UpdateCardTypeEvent,
CreatePeerEvent,
ThreadPatchEvent,
MessageEventType
} from '@hcengineering/communication-sdk-types'
import { getEmployee, getPersonSpaces } from '@hcengineering/server-contact'
import contact, { Employee, formatName, Person } from '@hcengineering/contact'
import communication, { Direct } from '@hcengineering/communication'
import { CardPeer } from '@hcengineering/communication-types'
async function OnAttribute (ctx: TxCreateDoc<AnyAttribute>[], control: TriggerControl): Promise<Tx[]> {
const attr = TxProcessor.createDoc2Doc(ctx[0])
if (control.hierarchy.isDerived(attr.attributeOf, card.class.Card)) {
const desc = control.hierarchy.getDescendants(attr.attributeOf)
const res: Tx[] = []
for (const des of desc) {
const viewlets = control.modelDb.findAllSync(view.class.Viewlet, { attachTo: des, variant: { $exists: false } })
for (const viewlet of viewlets) {
const updatedConfig = [...viewlet.config]
// let push it after grow for the list
if (viewlet.descriptor === view.viewlet.List) {
const index = viewlet.config.findIndex((p) => typeof p !== 'string' && p.displayProps?.grow === true)
if (index !== -1) {
updatedConfig.splice(index + 1, 0, attr.name)
} else {
updatedConfig.push(attr.name)
}
} else {
updatedConfig.push(attr.name)
}
res.push(
control.txFactory.createTxUpdateDoc(viewlet._class, viewlet.space, viewlet._id, {
config: updatedConfig
})
)
const prefs = await control.findAll(control.ctx, view.class.ViewletPreference, { attachedTo: viewlet._id })
for (const pref of prefs) {
const updatedPrefConfig = [...pref.config]
if (viewlet.descriptor === view.viewlet.List) {
const index = updatedPrefConfig.findIndex((p) => typeof p !== 'string' && p.displayProps?.grow === true)
if (index !== -1) {
updatedPrefConfig.splice(index + 1, 0, attr.name)
} else {
updatedPrefConfig.push(attr.name)
}
} else {
updatedPrefConfig.push(attr.name)
}
res.push(
control.txFactory.createTxUpdateDoc(pref._class, pref.space, pref._id, {
config: updatedPrefConfig
})
)
}
}
}
return res
}
return []
}
async function OnAttributeRemove (ctx: TxRemoveDoc<AnyAttribute>[], control: TriggerControl): Promise<Tx[]> {
const attr = control.removedMap.get(ctx[0].objectId) as AnyAttribute
if (attr === undefined) return []
if (control.hierarchy.isDerived(attr.attributeOf, card.class.Card)) {
const desc = control.hierarchy.getDescendants(attr.attributeOf)
const res: Tx[] = []
for (const des of desc) {
const viewlets = control.modelDb.findAllSync(view.class.Viewlet, { attachTo: des })
for (const viewlet of viewlets) {
res.push(
control.txFactory.createTxUpdateDoc(viewlet._class, viewlet.space, viewlet._id, {
config: viewlet.config.filter((p) => p !== attr.name)
})
)
const prefs = await control.findAll(control.ctx, view.class.ViewletPreference, { attachedTo: viewlet._id })
for (const pref of prefs) {
res.push(
control.txFactory.createTxUpdateDoc(pref._class, pref.space, pref._id, {
config: pref.config.filter((p) => p !== attr.name)
})
)
}
}
}
return res
}
return []
}
async function OnMasterTagRemove (ctx: TxUpdateDoc<MasterTag>[], control: TriggerControl): Promise<Tx[]> {
const updateTx = ctx[0]
if (updateTx.space === core.space.DerivedTx) return []
if (updateTx.operations.removed !== true) return []
const res: Tx[] = []
const desc = control.hierarchy.getDescendants(updateTx.objectId)
// should remove objects if masterTag
const cards = await control.findAll<Card>(control.ctx, updateTx.objectId, {})
for (const doc of cards) {
res.push(control.txFactory.createTxRemoveDoc(card.class.Card, doc.space, doc._id))
}
for (const des of desc) {
res.push(...(await removeTagRelations(control, des)))
}
for (const des of desc) {
if (des === updateTx.objectId) continue
const _class = control.hierarchy.findClass(des)
if (_class === undefined) continue
if (_class._class === card.class.MasterTag) {
res.push(
control.txFactory.createTxUpdateDoc<MasterTag>(card.class.MasterTag, core.space.Model, des, {
removed: true
})
)
} else {
res.push(control.txFactory.createTxRemoveDoc(card.class.Tag, core.space.Model, des))
}
}
return res
}
async function OnTagRemove (ctx: TxRemoveDoc<Tag>[], control: TriggerControl): Promise<Tx[]> {
const removeTx = ctx[0]
const removedTag = control.removedMap.get(removeTx.objectId)
if (removedTag === undefined) return []
const res: Tx[] = []
const desc = control.hierarchy.getDescendants(removeTx.objectId)
for (const des of desc) {
if (des === removeTx.objectId) continue
res.push(control.txFactory.createTxRemoveDoc(card.class.Tag, core.space.Model, des))
}
res.push(...(await removeTagRelations(control, removeTx.objectId)))
return res
}
async function removeTagRelations (control: TriggerControl, tag: Ref<Tag | MasterTag>): Promise<Tx[]> {
const res: Tx[] = []
const viewlets = await control.findAll(control.ctx, view.class.Viewlet, {
attachTo: tag
})
for (const viewlet of viewlets) {
res.push(control.txFactory.createTxRemoveDoc(viewlet._class, viewlet.space, viewlet._id))
}
const attributes = control.modelDb.findAllSync(core.class.Attribute, {
attributeOf: tag
})
for (const attribute of attributes) {
res.push(control.txFactory.createTxRemoveDoc(attribute._class, attribute.space, attribute._id))
}
const removedRelation = new Set()
const relationsA = control.modelDb.findAllSync(core.class.Association, {
classA: tag
})
for (const rel of relationsA) {
removedRelation.add(rel._id)
res.push(control.txFactory.createTxRemoveDoc(core.class.Association, core.space.Model, rel._id))
}
const relationsB = control.modelDb.findAllSync(core.class.Association, {
classB: tag
})
for (const rel of relationsB) {
if (removedRelation.has(rel._id)) continue
res.push(control.txFactory.createTxRemoveDoc(core.class.Association, core.space.Model, rel._id))
}
return res
}
function extractObjectData<T extends Doc> (doc: T): Data<T> {
const dataKeys = ['_id', 'space', 'modifiedOn', 'modifiedBy', 'createdBy', 'createdOn']
const data: any = {}
for (const key in doc) {
if (dataKeys.includes(key)) {
continue
}
data[key] = doc[key]
}
return data as Data<T>
}
async function OnMasterTagCreate (ctx: TxCreateDoc<MasterTag | Tag>[], control: TriggerControl): Promise<Tx[]> {
const createTx = ctx[0]
const tag = TxProcessor.createDoc2Doc(createTx)
const res: Tx[] = []
res.push(
control.txFactory.createTxMixin(createTx.objectId, core.class.Mixin, core.space.Model, setting.mixin.Editable, {
value: true
})
)
res.push(
control.txFactory.createTxMixin(createTx.objectId, core.class.Mixin, core.space.Model, setting.mixin.UserMixin, {})
)
if (tag._class === card.class.MasterTag) {
const viewlets = await control.findAll(control.ctx, view.class.Viewlet, {
attachTo: tag.extends,
variant: { $exists: false }
})
for (const viewlet of viewlets) {
const base = extractObjectData(viewlet)
res.push(
control.txFactory.createTxCreateDoc(view.class.Viewlet, core.space.Model, {
...base,
attachTo: createTx.objectId
})
)
}
}
return res
}
async function OnCardRemove (ctx: TxRemoveDoc<Card>[], control: TriggerControl): Promise<Tx[]> {
const removeTx = ctx[0]
const removedCard = control.removedMap.get(removeTx.objectId) as Card
if (removedCard === undefined) return []
const res: Tx[] = []
const cards = await control.findAll(control.ctx, card.class.Card, { parent: removedCard._id })
for (const card of cards) {
res.push(control.txFactory.createTxRemoveDoc(card._class, card.space, card._id))
}
const toDelete: string[] = []
for (const key in removedCard.blobs ?? {}) {
const val = removedCard.blobs[key]
if (val === undefined) continue
const toDelete: string[] = []
toDelete.push(val.file)
}
if (toDelete.length > 0) {
await control.storageAdapter.remove(control.ctx, control.workspace, toDelete)
}
if (removedCard.parent != null) {
res.push(
control.txFactory.createTxUpdateDoc(card.class.Card, core.space.Workspace, removedCard.parent, {
$inc: {
children: -1
}
})
)
}
const favorites = await control.findAll(control.ctx, card.class.FavoriteCard, { attachedTo: removedCard._id })
for (const favorite of favorites) {
res.push(control.txFactory.createTxRemoveDoc(favorite._class, favorite.space, favorite._id))
}
const event: RemoveCardEvent = {
type: CardEventType.RemoveCard,
cardId: removedCard._id,
date: new Date(removeTx.createdOn ?? removeTx.modifiedOn),
socialId: removedCard.modifiedBy
}
await control.domainRequest(control.ctx, 'communication' as OperationDomain, {
event
})
return res
}
function unwrapPush (push: Record<string, any>): Record<string, any> {
const res: Record<string, any> = {}
for (const [key, value] of Object.entries(push)) {
if (value.$each !== undefined) {
res[key] = value.$each
} else {
res[key] = value
}
}
return res
}
async function OnCardUpdate (ctx: TxUpdateDoc<Card>[], control: TriggerControl): Promise<Tx[]> {
const updateTx = ctx[0]
const doc = (await control.findAll(control.ctx, card.class.Card, { _id: updateTx.objectId }))[0]
if (doc === undefined) return []
const res: Tx[] = []
if (updateTx.operations.parent !== undefined) {
const newParent = updateTx.operations.parent
const oldParent = doc.parentInfo[doc.parentInfo.length - 1]?._id
if (newParent != null) {
const parent = (await control.findAll(control.ctx, card.class.Card, { _id: newParent }))[0]
if (parent !== undefined) {
if (parent.parentInfo.findIndex((p) => p._id === doc._id) === -1) {
res.push(
control.txFactory.createTxUpdateDoc(parent._class, parent.space, parent._id, {
$inc: {
children: 1
}
})
)
res.push(
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
parentInfo: [
...parent.parentInfo,
{
_id: parent._id,
_class: parent._class,
title: parent.title
}
]
})
)
} else {
// rollback
return [
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
parent: oldParent ?? null
})
]
}
}
}
if (oldParent != null) {
const parent = (await control.findAll(control.ctx, card.class.Card, { _id: oldParent }))[0]
if (parent !== undefined) {
res.push(
control.txFactory.createTxUpdateDoc(parent._class, parent.space, parent._id, {
$inc: {
children: -1
}
})
)
}
}
}
if (updateTx.operations.title !== undefined) {
res.push(...(await updateParentInfoName(control, doc._id, updateTx.operations.title, doc._id)))
}
if ((updateTx.operations as any)._class !== undefined) {
const event: UpdateCardTypeEvent = {
type: CardEventType.UpdateCardType,
cardId: doc._id,
cardType: (updateTx.operations as any)._class,
socialId: updateTx.createdBy ?? updateTx.modifiedBy,
date: new Date(updateTx.createdOn ?? updateTx.modifiedOn)
}
await control.domainRequest(control.ctx, 'communication' as OperationDomain, {
event
})
}
res.push(...(await updatePeers(control, doc, updateTx)))
await updateCollaborators(control, updateTx.operations, doc._class, doc, updateTx.modifiedBy)
return res
}
function getUpdateEmployees (
control: TriggerControl,
field: string,
value: any,
_class: Ref<Class<Doc>>
): Ref<Employee>[] | undefined {
const res: Ref<Employee>[] = []
const attr = control.hierarchy.findAttribute(_class, field)
if (attr === undefined) return
const parentType = attr.type._class === core.class.ArrOf ? (attr.type as ArrOf<Doc>).of : attr.type
if (parentType._class !== core.class.RefTo) return
const to = (parentType as RefTo<Doc>).to
if (control.hierarchy.isDerived(to, contact.mixin.Employee)) {
if (value != null) {
if (Array.isArray(value)) {
for (const val of value) {
res.push(val)
}
} else {
res.push(value)
}
}
}
return res
}
async function addCollaborators (
collaboratorRefs: Set<Ref<Employee>>,
control: TriggerControl,
doc: Card,
modifiedBy: PersonId
): Promise<void> {
if (collaboratorRefs.size > 0) {
const employees = await control.findAll(control.ctx, contact.mixin.Employee, {
_id: { $in: [...collaboratorRefs] }
})
const collaborators = employees.map((p) => p.personUuid).filter((p) => p != null)
const event: AddCollaboratorsEvent = {
type: NotificationEventType.AddCollaborators,
cardId: doc._id,
cardType: doc._class,
collaborators,
socialId: modifiedBy
}
await control.domainRequest(control.ctx, 'communication' as OperationDomain, {
event
})
}
}
async function updateCollaborators (
control: TriggerControl,
ops: MixinUpdate<Card, Card> | DocumentUpdate<Card>,
_class: Ref<Class<Doc>>,
doc: Card,
modifiedBy: PersonId
): Promise<void> {
const collaboratorRefs = new Set<Ref<Employee>>()
for (const [field, value] of Object.entries(ops)) {
if (field === '$push') {
const unwrap = unwrapPush(value)
for (const [field, value] of Object.entries(unwrap)) {
const col = getUpdateEmployees(control, field, value, _class)
if (col !== undefined) {
col.map((p) => collaboratorRefs.add(p))
}
}
}
if (field.startsWith('$')) continue
const col = getUpdateEmployees(control, field, value, _class)
if (col !== undefined) {
col.map((p) => collaboratorRefs.add(p))
}
}
await addCollaborators(collaboratorRefs, control, doc, modifiedBy)
}
async function updatePeers (control: TriggerControl, doc: Card, updateTx: TxUpdateDoc<Card>): Promise<Tx[]> {
if (updateTx.space === core.space.DerivedTx) return []
const isDirect = control.hierarchy.isDerived(doc._class, communication.type.Direct)
const isThreadFromDirect = (doc.parentInfo ?? []).some((it) =>
control.hierarchy.isDerived(it._class, communication.type.Direct)
)
if (!isDirect && !isThreadFromDirect) return []
delete updateTx.operations.title
delete updateTx.operations.parentInfo
delete updateTx.operations.parent
delete updateTx.operations.$inc
const peers = (
(
await control.domainRequest(control.ctx, 'communication' as OperationDomain, {
findPeers: { params: { kind: 'card', cardId: doc._id } }
})
).value as CardPeer[]
).flatMap((it) => it.members)
const res: Tx[] = []
for (const peer of peers) {
res.push(control.txFactory.createTxUpdateDoc(doc._class, peer.extra.space, peer.cardId, updateTx.operations))
}
return res
}
async function updateParentInfoName (
control: TriggerControl,
parent: Ref<Card>,
title: string,
originParent: Ref<Card>
): Promise<Tx[]> {
const res: Tx[] = []
const childs = await control.findAll(control.ctx, card.class.Card, { parent })
for (const child of childs) {
if (child._id === originParent) continue
const parentInfo = child.parentInfo
const index = parentInfo.findIndex((p) => p._id === parent)
if (index === -1) {
continue
}
parentInfo[index].title = title
res.push(
control.txFactory.createTxUpdateDoc(child._class, child.space, child._id, {
parentInfo
})
)
res.push(...(await updateParentInfoName(control, child._id, title, originParent)))
}
return res
}
async function OnThreadCreate (ctx: TxCreateDoc<Card>[], control: TriggerControl): Promise<Tx[]> {
const res: Tx[] = []
for (const tx of ctx) {
if (tx.space === core.space.DerivedTx) continue
const doc = TxProcessor.createDoc2Doc(tx)
const parent = doc.parentInfo?.[0]
if (parent == null) continue
if (!control.hierarchy.isDerived(parent._class, communication.type.Direct)) continue
const direct = (await control.findAll(control.ctx, parent._class, { _id: parent._id }, { limit: 1 }))[0] as Direct
if (direct == null) continue
res.push(...(await createThreadCardPeers(direct, doc, control)))
}
return res
}
async function createThreadCardPeers (direct: Direct, doc: Card, control: TriggerControl): Promise<Tx[]> {
const res: Tx[] = []
const cardIds = new Map<Ref<Card>, Ref<Space>>([[doc._id, doc.space]])
const members = direct.members ?? []
if (members.length === 0) return []
const thread = (
await control.domainRequest(control.ctx, 'communication' as OperationDomain, {
findThreads: { params: { threadId: doc._id } }
})
).value[0]
if (thread === undefined) return []
const messageId = thread.messageId
const directPeer = (
(
await control.domainRequest(control.ctx, 'communication' as OperationDomain, {
findPeers: { params: { kind: 'card', cardId: direct._id } }
})
).value as CardPeer[]
)[0]
const personSpaces = (await getPersonSpaces(control)).filter(
(it) => it._id !== doc.space && members.includes(it.person)
)
if (personSpaces.length === 0) return []
const accounts = (
await control.findAll(control.ctx, contact.mixin.Employee, {
_id: { $in: personSpaces.map((it) => it.person) as Ref<Employee>[] }
})
)
.map((it) => it.personUuid)
.filter(notEmpty)
if (accounts.length === 0) return []
// TODO: create directs in person_workspace
for (const personSpace of personSpaces) {
const _id = generateId<Card>()
const _class = doc._class
cardIds.set(_id, personSpace._id)
res.push(
control.txFactory.createTxCreateDoc(
_class,
personSpace._id,
{
...doc
},
_id
)
)
const parentDirect = directPeer?.members?.find((m) => m.extra?.space === personSpace._id)
if (parentDirect !== undefined) {
const threadPatchEvent: ThreadPatchEvent = {
type: MessageEventType.ThreadPatch,
cardId: parentDirect.cardId,
messageId,
operation: {
opcode: 'attach',
threadId: _id,
threadType: _class
},
socialId: doc.modifiedBy
}
await control.domainRequest(control.ctx, 'communication' as OperationDomain, {
event: threadPatchEvent
})
}
}
if (cardIds.size > 1) {
const group = generateId()
for (const [cardId, spaceId] of cardIds.entries()) {
const event: CreatePeerEvent = {
type: PeerEventType.CreatePeer,
workspaceId: control.workspace.uuid, // TODO: person_workspace
cardId,
kind: 'card',
value: group,
extra: { space: spaceId },
date: new Date(doc.modifiedOn)
}
await control.domainRequest(control.ctx, 'communication' as OperationDomain, { event })
}
}
return res
}
function getDirectTitle (employees: Employee[], me: Ref<Person>): string {
if (employees.length === 1) {
return employees.map((e) => formatName(e.name)).join(', ')
} else {
return employees
.filter((it) => it._id !== me)
.map((e) => formatName(e.name))
.join(', ')
}
}
async function createDirectCardPeers (doc: Card, members: Ref<Person>[], control: TriggerControl): Promise<Tx[]> {
const res: Tx[] = []
const cardIds = new Map<Ref<Card>, Ref<Space>>([[doc._id, doc.space]])
if (members.length === 0) return []
const personSpaces = (await getPersonSpaces(control)).filter((it) => members.includes(it.person))
if (personSpaces.length <= 1) return []
const employees = await control.findAll(control.ctx, contact.mixin.Employee, {
_id: { $in: personSpaces.map((it) => it.person) as Ref<Employee>[] }
})
const accounts = employees.map((it) => it.personUuid).filter(notEmpty)
if (accounts.length === 0) return []
// TODO: create directs in person_workspace
for (const personSpace of personSpaces) {
if (personSpace._id === doc.space) continue
const _id = generateId<Card>()
const _class = doc._class
cardIds.set(_id, personSpace._id)
const title = getDirectTitle(employees, personSpace.person)
res.push(
control.txFactory.createTxCreateDoc(
_class,
personSpace._id,
{
...doc,
title
},
_id
)
)
const event: AddCollaboratorsEvent = {
type: NotificationEventType.AddCollaborators,
cardId: _id,
cardType: _class,
collaborators: accounts,
socialId: doc.modifiedBy,
date: new Date(doc.modifiedOn + 1)
}
await control.domainRequest(control.ctx, 'communication' as OperationDomain, { event })
}
if (cardIds.size > 1) {
const group = generateId()
for (const [cardId, spaceId] of cardIds.entries()) {
const event: CreatePeerEvent = {
type: PeerEventType.CreatePeer,
workspaceId: control.workspace.uuid, // TODO: person_workspace
cardId,
kind: 'card',
value: group,
extra: { space: spaceId },
date: new Date(doc.modifiedOn)
}
await control.domainRequest(control.ctx, 'communication' as OperationDomain, { event })
}
}
return res
}
async function OnDirectCreate (ctx: TxCreateDoc<Direct>[], control: TriggerControl): Promise<Tx[]> {
const res: Tx[] = []
for (const tx of ctx) {
if (tx.space === core.space.DerivedTx) continue
const doc = TxProcessor.createDoc2Doc(tx)
const members = doc.members ?? []
res.push(...(await createDirectCardPeers(doc, members, control)))
}
return res
}
async function OnCardCreate (ctx: TxCreateDoc<Card>[], control: TriggerControl): Promise<Tx[]> {
const createTx = ctx[0]
const doc = TxProcessor.createDoc2Doc(createTx)
const res: Tx[] = []
if (doc.parent != null) {
const parent = (await control.findAll(control.ctx, card.class.Card, { _id: doc.parent }))[0]
if (parent !== undefined) {
res.push(
control.txFactory.createTxUpdateDoc(parent._class, parent.space, parent._id, {
$inc: {
children: 1
}
})
)
if ((doc.parentInfo?.length ?? 0) === 0) {
const parentInfo = [
...(parent.parentInfo ?? []),
{
_id: parent._id,
_class: parent._class,
title: parent.title
}
]
res.push(
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
parentInfo
})
)
}
}
}
await createCollaborators(control, ctx)
return res
}
async function createCollaborators (control: TriggerControl, ctx: TxCreateDoc<Card>[]): Promise<void> {
for (const tx of ctx) {
const modifier = await getEmployee(control, tx.modifiedBy)
const collaborators: AccountUuid[] = []
if (modifier?.personUuid != null && modifier.active) {
collaborators.push(modifier.personUuid)
}
const personSpaces = await getPersonSpaces(control)
const personSpace = personSpaces.find((it) => it._id === tx.objectSpace)
if (personSpace != null && personSpace.person !== modifier?._id) {
const spacePerson = (await control.findAll(control.ctx, contact.class.Person, { _id: personSpace.person }))[0]
if (spacePerson?.personUuid != null) {
collaborators.push(spacePerson.personUuid as AccountUuid)
}
}
if (collaborators.length === 0) continue
const event: AddCollaboratorsEvent = {
type: NotificationEventType.AddCollaborators,
cardId: tx.objectId,
cardType: tx.objectClass,
collaborators,
socialId: tx.createdBy ?? tx.modifiedBy,
date: new Date((tx.createdOn ?? tx.modifiedOn) + 1)
}
await control.domainRequest(control.ctx, 'communication' as OperationDomain, {
event
})
}
}
export async function OnCardTag (ctx: TxMixin<Card, Card>[], control: TriggerControl): Promise<Tx[]> {
const res: Tx[] = []
for (const tx of ctx) {
if (tx.space === core.space.DerivedTx) continue
if (tx._class !== core.class.TxMixin) continue
const target = tx.mixin
const to = control.hierarchy.getBaseClass(target)
const ancestors = control.hierarchy.getAncestors(target).filter((p) => control.hierarchy.isDerived(p, to))
const mixinAncestors: Ref<Mixin<Doc>>[] = []
const doc = (await control.findAll(control.ctx, tx.objectClass, { _id: tx.objectId }))[0]
if (doc === undefined) continue
for (const anc of ancestors) {
if (anc === target) continue
if (control.hierarchy.hasMixin(doc, anc)) break
if (anc === to) break
mixinAncestors.unshift(anc)
}
for (const anc of mixinAncestors) {
res.push(control.txFactory.createTxMixin(doc._id, doc._class, doc.space, anc, {}))
}
const updated = fillDefaults(control.hierarchy, control.hierarchy.as(control.hierarchy.clone(doc), target), target)
const diff = getDiffUpdate(doc, updated)
const splitted = splitMixinUpdate(control.hierarchy, diff, target, doc._class)
for (const it of splitted) {
if (control.hierarchy.isMixin(it[0])) {
res.push(control.txFactory.createTxMixin(doc._id, doc._class, doc.space, it[0], it[1]))
} else {
res.push(control.txFactory.createTxUpdateDoc(it[0], doc.space, doc._id, it[1]))
}
}
await updateCollaborators(control, tx.attributes, tx.mixin, doc, tx.modifiedBy)
}
return res
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({
trigger: {
OnAttribute,
OnAttributeRemove,
OnMasterTagCreate,
OnMasterTagRemove,
OnTagRemove,
OnCardRemove,
OnCardCreate,
OnCardUpdate,
OnCardTag,
OnDirectCreate,
OnThreadCreate
}
})