platform/server-plugins/contact-resources/src/index.ts
Andrey Sobolev c01a274cef
UBERF-7690: Trigger improvements (#6340)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
2024-08-19 10:41:31 +05:00

387 lines
12 KiB
TypeScript

//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 2022, 2023 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 contact, {
Channel,
Contact,
Employee,
Organization,
Person,
PersonAccount,
PersonSpace,
contactId,
formatContactName,
formatName,
getFirstName,
getLastName,
getName
} from '@hcengineering/contact'
import core, {
Account,
Class,
Doc,
Hierarchy,
Ref,
SpaceType,
Tx,
TxCUD,
TxCreateDoc,
TxMixin,
TxProcessor,
TxRemoveDoc,
TxUpdateDoc,
concatLink
} from '@hcengineering/core'
import notification, { Collaborators } from '@hcengineering/notification'
import { getMetadata } from '@hcengineering/platform'
import serverCore, { TriggerControl, removeAllObjects } from '@hcengineering/server-core'
import { workbenchId } from '@hcengineering/workbench'
export async function OnSpaceTypeMembers (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const ctx = tx as TxUpdateDoc<SpaceType>
const result: Tx[] = []
const newMember = ctx.operations.$push?.members as Ref<Account>
if (newMember !== undefined) {
const spaces = await control.findAll(core.class.Space, { type: ctx.objectId })
for (const space of spaces) {
if (space.members.includes(newMember)) continue
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
$push: {
members: newMember
}
})
result.push(pushTx)
}
}
const oldMember = ctx.operations.$pull?.members as Ref<Account>
if (ctx.operations.$pull?.members !== undefined) {
const spaces = await control.findAll(core.class.Space, { type: ctx.objectId })
for (const space of spaces) {
if (!space.members.includes(oldMember)) continue
const pullTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
$pull: {
members: oldMember
}
})
result.push(pullTx)
}
}
return result
}
export async function OnEmployeeCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const mixinTx = tx as TxMixin<Person, Employee>
if (mixinTx.attributes.active !== true) return []
const acc = control.modelDb.getAccountByPersonId(mixinTx.objectId)
if (acc.length === 0) return []
const spaces = await control.findAll(core.class.Space, { autoJoin: true })
const result: Tx[] = []
const txes = await createPersonSpace(
acc.map((it) => it._id),
mixinTx.objectId,
control
)
result.push(...txes)
for (const space of spaces) {
const toAdd = acc.filter((it) => !space.members.includes(it._id))
if (toAdd.length === 0) continue
for (const a of toAdd) {
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
$push: {
members: a._id
}
})
result.push(pushTx)
}
}
return result
}
async function createPersonSpace (
account: Ref<Account>[],
person: Ref<Person>,
control: TriggerControl
): Promise<TxCUD<PersonSpace>[]> {
const personSpace = (await control.findAll(contact.class.PersonSpace, { person }, { limit: 1 })).shift()
if (personSpace !== undefined) {
const toAdd = account.filter((it) => !personSpace.members.includes(it))
if (toAdd.length === 0) return []
return toAdd.map((it) =>
control.txFactory.createTxUpdateDoc(personSpace._class, personSpace.space, personSpace._id, {
$push: {
members: it
}
})
)
}
return [
control.txFactory.createTxCreateDoc(contact.class.PersonSpace, core.space.Space, {
name: 'Personal space',
description: '',
private: true,
archived: false,
person,
members: account
})
]
}
export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const acc = TxProcessor.createDoc2Doc(tx as TxCreateDoc<PersonAccount>)
const person = (
await control.findAll(contact.mixin.Employee, { _id: acc.person as Ref<Employee>, active: true }, { limit: 1 })
)[0]
if (person === undefined) return []
const spaces = await control.findAll(core.class.Space, { autoJoin: true })
const result: Tx[] = []
const txes = await createPersonSpace([acc._id], person._id, control)
result.push(...txes)
for (const space of spaces) {
if (space.members.includes(acc._id)) continue
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
$push: {
members: acc._id
}
})
result.push(pushTx)
}
return result
}
/**
* @public
*/
export async function OnContactDelete (
tx: Tx,
{ findAll, hierarchy, storageAdapter, workspace, removedMap, txFactory, ctx }: TriggerControl
): Promise<Tx[]> {
const rmTx = tx as TxRemoveDoc<Contact>
const removeContact = removedMap.get(rmTx.objectId) as Contact
if (removeContact === undefined) {
return []
}
if (removeContact.avatar == null) {
return []
}
await removeAllObjects(ctx, storageAdapter, workspace, removeContact.avatar)
const result: Tx[] = []
const members = await findAll(contact.class.Member, { contact: removeContact._id })
for (const member of members) {
const removeTx = txFactory.createTxRemoveDoc(member._class, member.space, member._id)
const tx = txFactory.createTxCollectionCUD(
member.attachedToClass,
member.attachedTo,
member.space,
member.collection,
removeTx
)
result.push(tx)
}
return result
}
/**
* @public
*/
export async function OnChannelUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const uTx = tx as TxUpdateDoc<Channel>
const result: Tx[] = []
if (uTx.operations.$inc?.items !== undefined) {
const doc = (await control.findAll(uTx.objectClass, { _id: uTx.objectId }, { limit: 1 }))[0]
if (doc !== undefined) {
if (control.hierarchy.hasMixin(doc, notification.mixin.Collaborators)) {
const collab = control.hierarchy.as(doc, notification.mixin.Collaborators) as Doc as Collaborators
if (collab.collaborators.includes(tx.modifiedBy)) {
result.push(
control.txFactory.createTxMixin(doc._id, doc._class, doc.space, notification.mixin.Collaborators, {
$push: {
collaborators: tx.modifiedBy
}
})
)
}
} else {
const res = control.txFactory.createTxMixin<Doc, Collaborators>(
doc._id,
doc._class,
doc.space,
notification.mixin.Collaborators,
{
collaborators: [tx.modifiedBy]
}
)
result.push(res)
}
}
}
return result
}
/**
* @public
*/
export async function personHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
const person = doc as Person
const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? ''
const path = `${workbenchId}/${control.workspace.workspaceUrl}/${contactId}/${doc._id}`
const link = concatLink(front, path)
return `<a href="${link}">${getName(control.hierarchy, person, control.branding?.lastNameFirst)}</a>`
}
/**
* @public
*/
export function personTextPresenter (doc: Doc, control: TriggerControl): string {
const person = doc as Person
return `${getName(control.hierarchy, person, control.branding?.lastNameFirst)}`
}
/**
* @public
*/
export async function organizationHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
const organization = doc as Organization
const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? ''
const path = `${workbenchId}/${control.workspace.workspaceUrl}/${contactId}/${doc._id}`
const link = concatLink(front, path)
return `<a href="${link}">${organization.name}</a>`
}
/**
* @public
*/
export function organizationTextPresenter (doc: Doc): string {
const organization = doc as Organization
return `${organization.name}`
}
/**
* @public
*/
export function contactNameProvider (hierarchy: Hierarchy, props: Record<string, string>): string {
const _class = props._class !== undefined ? (props._class as Ref<Class<Doc>>) : contact.class.Contact
return formatContactName(hierarchy, _class, props.name ?? '', props.lastNameFirst)
}
export async function getCurrentEmployeeName (control: TriggerControl, context: Record<string, Doc>): Promise<string> {
const account = await control.modelDb.findOne(contact.class.PersonAccount, {
_id: control.txFactory.account as Ref<PersonAccount>
})
if (account === undefined) return ''
const employee = (await control.findAll(contact.class.Person, { _id: account.person }))[0]
return employee !== undefined ? formatName(employee.name, control.branding?.lastNameFirst) : ''
}
export async function getCurrentEmployeeEmail (control: TriggerControl, context: Record<string, Doc>): Promise<string> {
const account = await control.modelDb.findOne(contact.class.PersonAccount, {
_id: control.txFactory.account as Ref<PersonAccount>
})
if (account === undefined) return ''
return account.email
}
export async function getCurrentEmployeePosition (
control: TriggerControl,
context: Record<string, Doc>
): Promise<string | undefined> {
const account = await control.modelDb.findOne(contact.class.PersonAccount, {
_id: control.txFactory.account as Ref<PersonAccount>
})
if (account === undefined) return ''
const employee = (await control.findAll(contact.class.Person, { _id: account.person }))[0]
if (employee !== undefined) {
return control.hierarchy.as(employee, contact.mixin.Employee)?.position ?? ''
}
}
export async function getContactName (
control: TriggerControl,
context: Record<string, Doc>
): Promise<string | undefined> {
const value = context[contact.class.Contact] as Contact
if (value === undefined) return
if (control.hierarchy.isDerived(value._class, contact.class.Person)) {
return getName(control.hierarchy, value, control.branding?.lastNameFirst)
} else {
return value.name
}
}
export async function getContactLastName (
control: TriggerControl,
context: Record<string, Doc>
): Promise<string | undefined> {
const value = context[contact.class.Contact] as Contact
if (value === undefined) return
if (control.hierarchy.isDerived(value._class, contact.class.Person)) {
return getLastName(value.name)
} else {
return ''
}
}
export async function getContactFirstName (
control: TriggerControl,
context: Record<string, Doc>
): Promise<string | undefined> {
const value = context[contact.class.Contact] as Contact
if (value === undefined) return
if (control.hierarchy.isDerived(value._class, contact.class.Person)) {
return getFirstName(value.name)
} else {
return value.name
}
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({
trigger: {
OnEmployeeCreate,
OnPersonAccountCreate,
OnContactDelete,
OnChannelUpdate,
OnSpaceTypeMembers
},
function: {
PersonHTMLPresenter: personHTMLPresenter,
PersonTextPresenter: personTextPresenter,
OrganizationHTMLPresenter: organizationHTMLPresenter,
OrganizationTextPresenter: organizationTextPresenter,
ContactNameProvider: contactNameProvider,
GetCurrentEmployeeName: getCurrentEmployeeName,
GetCurrentEmployeeEmail: getCurrentEmployeeEmail,
GetContactName: getContactName,
GetCurrentEmployeePosition: getCurrentEmployeePosition,
GetContactFirstName: getContactFirstName,
GetContactLastName: getContactLastName
}
})