diff --git a/models/contact/src/index.ts b/models/contact/src/index.ts index b283134187..12d1e7f8e7 100644 --- a/models/contact/src/index.ts +++ b/models/contact/src/index.ts @@ -24,9 +24,9 @@ import type { Person, Persons } from '@anticrm/contact' -import type { Domain, Ref } from '@anticrm/core' +import type { Domain, Ref, Timestamp } from '@anticrm/core' import { DOMAIN_MODEL, IndexKind } from '@anticrm/core' -import { Builder, Collection, Index, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model' +import { Builder, Collection, Index, Model, Prop, TypeRef, TypeString, TypeTimestamp, UX } from '@anticrm/model' import attachment from '@anticrm/model-attachment' import chunter from '@anticrm/model-chunter' import core, { TAccount, TAttachedDoc, TDoc, TSpace } from '@anticrm/model-core' @@ -70,7 +70,7 @@ export class TContact extends TDoc implements Contact { } @Model(contact.class.Channel, core.class.AttachedDoc, DOMAIN_CHANNEL) -@UX(contact.string.Channel, contact.icon.Person, undefined, 'modifiedOn') +@UX(contact.string.Channel, contact.icon.Person, undefined, 'lastMessage') export class TChannel extends TAttachedDoc implements Channel { @Prop(TypeRef(contact.class.ChannelProvider), contact.string.ChannelProvider) provider!: Ref @@ -80,6 +80,9 @@ export class TChannel extends TAttachedDoc implements Channel { value!: string items?: number + + @Prop(TypeTimestamp(), core.string.Modified) + lastMessage?: Timestamp } @Model(contact.class.Person, contact.class.Contact) diff --git a/models/gmail/package.json b/models/gmail/package.json index 1169ebdab5..7a29e03bd8 100644 --- a/models/gmail/package.json +++ b/models/gmail/package.json @@ -30,6 +30,7 @@ "@anticrm/core": "~0.6.16", "@anticrm/platform": "~0.6.6", "@anticrm/model-core": "~0.6.0", + "@anticrm/contact": "~0.6.5", "@anticrm/model-contact": "~0.6.1", "@anticrm/gmail": "~0.6.0", "@anticrm/gmail-resources": "~0.6.0", diff --git a/models/gmail/src/index.ts b/models/gmail/src/index.ts index 3019cd9223..c1af64e64a 100644 --- a/models/gmail/src/index.ts +++ b/models/gmail/src/index.ts @@ -1,6 +1,5 @@ // -// Copyright © 2020, 2021 Anticrm Platform Contributors. -// Copyright © 2021 Hardcore Engineering Inc. +// Copyright © 2022 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 @@ -15,12 +14,13 @@ // import activity from '@anticrm/activity' -import { Domain, IndexKind, Timestamp, Type } from '@anticrm/core' +import { Channel } from '@anticrm/contact' +import { Class, Domain, IndexKind, Ref, Timestamp, Type } from '@anticrm/core' import type { Message, NewMessage, SharedMessage, SharedMessages } from '@anticrm/gmail' import { ArrOf, Builder, Collection, Index, Model, Prop, TypeBoolean, TypeString, TypeTimestamp } from '@anticrm/model' -import contact from '@anticrm/model-contact' -import core, { TDoc, TAttachedDoc } from '@anticrm/model-core' import attachment from '@anticrm/model-attachment' +import contact from '@anticrm/model-contact' +import core, { TAttachedDoc, TDoc } from '@anticrm/model-core' import setting from '@anticrm/setting' import gmail from './plugin' @@ -32,6 +32,9 @@ function TypeSharedMessage (): Type { @Model(gmail.class.Message, core.class.AttachedDoc, DOMAIN_GMAIL) export class TMessage extends TAttachedDoc implements Message { + declare attachedTo: Ref + declare attachedToClass: Ref> + @Prop(TypeString(), gmail.string.MessageID) messageId!: string diff --git a/models/server-gmail/src/index.ts b/models/server-gmail/src/index.ts index c9c491d5cc..46b8a9d3ce 100644 --- a/models/server-gmail/src/index.ts +++ b/models/server-gmail/src/index.ts @@ -29,4 +29,8 @@ export function createModel (builder: Builder): void { collectDocs: serverGmail.function.FindMessages } ) + + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverGmail.trigger.OnMessageCreate + }) } diff --git a/models/server-telegram/src/index.ts b/models/server-telegram/src/index.ts index 1ef5728232..94677c1d08 100644 --- a/models/server-telegram/src/index.ts +++ b/models/server-telegram/src/index.ts @@ -29,4 +29,8 @@ export function createModel (builder: Builder): void { collectDocs: serverTelegram.function.FindMessages } ) + + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverTelegram.trigger.OnMessageCreate + }) } diff --git a/models/telegram/package.json b/models/telegram/package.json index 0da2a2a8a7..dbf91e3445 100644 --- a/models/telegram/package.json +++ b/models/telegram/package.json @@ -32,6 +32,7 @@ "@anticrm/model-core": "~0.6.0", "@anticrm/model-attachment": "~0.6.0", "@anticrm/model-contact": "~0.6.1", + "@anticrm/contact": "~0.6.5", "@anticrm/telegram": "~0.6.2", "@anticrm/telegram-resources": "~0.6.0", "@anticrm/setting": "~0.6.1", diff --git a/models/telegram/src/index.ts b/models/telegram/src/index.ts index 919ff189e5..90e07c75f2 100644 --- a/models/telegram/src/index.ts +++ b/models/telegram/src/index.ts @@ -24,9 +24,10 @@ import type { SharedTelegramMessage, SharedTelegramMessages } from '@anticrm/telegram' -import { Domain, IndexKind, Timestamp, Type } from '@anticrm/core' +import { Class, Domain, IndexKind, Ref, Timestamp, Type } from '@anticrm/core' import setting from '@anticrm/setting' import activity from '@anticrm/activity' +import { Channel } from '@anticrm/contact' import attachment from '@anticrm/model-attachment' export const DOMAIN_TELEGRAM = 'telegram' as Domain @@ -37,6 +38,9 @@ function TypeSharedMessage (): Type { @Model(telegram.class.Message, core.class.AttachedDoc, DOMAIN_TELEGRAM) export class TTelegramMessage extends TAttachedDoc implements TelegramMessage { + declare attachedTo: Ref + declare attachedToClass: Ref> + @Prop(TypeString(), telegram.string.Content) @Index(IndexKind.FullText) content!: string diff --git a/models/telegram/src/migration.ts b/models/telegram/src/migration.ts index 49dd4652a3..a950586183 100644 --- a/models/telegram/src/migration.ts +++ b/models/telegram/src/migration.ts @@ -13,9 +13,31 @@ // limitations under the License. // -import core, { TxOperations } from '@anticrm/core' +import core, { SortingOrder, TxOperations } from '@anticrm/core' import telegram from './plugin' import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model' +import contact from '@anticrm/model-contact' + +async function updateChannlLastMessage (client: TxOperations): Promise { + const channels = await client.findAll(contact.class.Channel, { + provider: contact.channelProvider.Telegram + }) + const targets = channels.filter((p) => p.lastMessage === undefined) + for (const channel of targets) { + const lastMessage = await client.findOne( + telegram.class.Message, + { + attachedTo: channel._id + }, + { sort: { sendOn: SortingOrder.Descending } } + ) + if (lastMessage !== undefined) { + await client.updateDoc(channel._class, channel.space, channel._id, { + lastMessage: lastMessage.sendOn + }) + } + } +} export const telegramOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise {}, @@ -38,5 +60,7 @@ export const telegramOperation: MigrateOperation = { telegram.space.Telegram ) } + + await updateChannlLastMessage(tx) } } diff --git a/plugins/contact-resources/src/components/ChannelsDropdown.svelte b/plugins/contact-resources/src/components/ChannelsDropdown.svelte index 200a60e422..96189a9296 100644 --- a/plugins/contact-resources/src/components/ChannelsDropdown.svelte +++ b/plugins/contact-resources/src/components/ChannelsDropdown.svelte @@ -73,8 +73,9 @@ } function isNew (item: Channel, lastViews: Map, Timestamp>): boolean { + if (item.lastMessage === undefined) return false const lastView = (item as Channel)._id !== undefined ? lastViews.get((item as Channel)._id) : undefined - return lastView ? lastView < item.modifiedOn : (item.items ?? 0) > 0 + return lastView ? lastView < item.lastMessage : (item.items ?? 0) > 0 } async function update (value: AttachedData[] | Channel | null, lastViews: Map, Timestamp>) { diff --git a/plugins/contact-resources/src/components/ChannelsView.svelte b/plugins/contact-resources/src/components/ChannelsView.svelte index 5bb6e5dfa6..ba28b4e7fd 100644 --- a/plugins/contact-resources/src/components/ChannelsView.svelte +++ b/plugins/contact-resources/src/components/ChannelsView.svelte @@ -65,8 +65,9 @@ } function isNew (item: Channel, lastViews: Map, Timestamp>): boolean { + if (item.lastMessage === undefined) return false const lastView = (item as Channel)._id !== undefined ? lastViews.get((item as Channel)._id) : undefined - return lastView ? lastView < item.modifiedOn : (item.items ?? 0) > 0 + return lastView ? lastView < item.lastMessage : (item.items ?? 0) > 0 } async function update (value: AttachedData[] | Channel | null, lastViews: Map, Timestamp>) { diff --git a/plugins/contact/src/index.ts b/plugins/contact/src/index.ts index 12bedcbe04..b26e6f0ed5 100644 --- a/plugins/contact/src/index.ts +++ b/plugins/contact/src/index.ts @@ -24,6 +24,7 @@ import { FindResult, Ref, Space, + Timestamp, UXObject } from '@anticrm/core' import type { Asset, Plugin } from '@anticrm/platform' @@ -56,6 +57,7 @@ export interface Channel extends AttachedDoc { provider: Ref value: string items?: number + lastMessage?: Timestamp } /** diff --git a/plugins/gmail/src/index.ts b/plugins/gmail/src/index.ts index c1782aa449..fdd57593a5 100644 --- a/plugins/gmail/src/index.ts +++ b/plugins/gmail/src/index.ts @@ -18,11 +18,14 @@ import type { Plugin } from '@anticrm/platform' import type { Doc, Ref, Class, Space, AttachedDoc, Timestamp } from '@anticrm/core' import type { AnyComponent } from '@anticrm/ui' import type { IntegrationType, Handler } from '@anticrm/setting' +import { Channel } from '@anticrm/contact' /** * @public */ export interface Message extends BaseMessage, AttachedDoc { + attachedTo: Ref + attachedToClass: Ref> messageId: string from: string textContent: string diff --git a/plugins/telegram/src/index.ts b/plugins/telegram/src/index.ts index 325dbffe1f..7d0c0ba7f1 100644 --- a/plugins/telegram/src/index.ts +++ b/plugins/telegram/src/index.ts @@ -18,6 +18,7 @@ import type { Plugin } from '@anticrm/platform' import type { Doc, Ref, Class, Space, AttachedDoc, Timestamp } from '@anticrm/core' import type { AnyComponent } from '@anticrm/ui' import type { IntegrationType, Handler } from '@anticrm/setting' +import { Channel } from '@anticrm/contact' /** * @public @@ -31,6 +32,9 @@ export interface BaseTelegramMessage extends Doc { * @public */ export interface TelegramMessage extends BaseTelegramMessage, AttachedDoc { + attachedTo: Ref + attachedToClass: Ref> + incoming: boolean sendOn: Timestamp diff --git a/server-plugins/gmail-resources/src/index.ts b/server-plugins/gmail-resources/src/index.ts index d6d29c219a..77fcb2e6fe 100644 --- a/server-plugins/gmail-resources/src/index.ts +++ b/server-plugins/gmail-resources/src/index.ts @@ -14,8 +14,38 @@ // import contact, { Channel } from '@anticrm/contact' -import gmail from '@anticrm/gmail' -import { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref } from '@anticrm/core' +import core, { + AttachedDoc, + Class, + Doc, + DocumentQuery, + FindOptions, + FindResult, + Hierarchy, + Ref, + Tx, + TxCollectionCUD, + TxCreateDoc, + TxProcessor +} from '@anticrm/core' +import gmail, { Message } from '@anticrm/gmail' +import { TriggerControl } from '@anticrm/server-core' + +const extractTx = (tx: Tx): Tx => { + if (tx._class === core.class.TxCollectionCUD) { + const ctx = tx as TxCollectionCUD + if (ctx.tx._class === core.class.TxCreateDoc) { + const create = ctx.tx as TxCreateDoc + create.attributes.attachedTo = ctx.objectId + create.attributes.attachedToClass = ctx.objectClass + create.attributes.collection = ctx.collection + return create + } + return ctx.tx + } + + return tx +} /** * @public @@ -33,13 +63,45 @@ export async function FindMessages ( if (channel.provider !== contact.channelProvider.Email) { return [] } - const messages = await findAll(gmail.class.Message, { attachedTo: doc._id }) - const newMessages = await findAll(gmail.class.NewMessage, { attachedTo: doc._id }) + const messages = await findAll(gmail.class.Message, { attachedTo: channel._id }) + const newMessages = await findAll(gmail.class.NewMessage, { attachedTo: channel._id }) return [...messages, ...newMessages] } +/** + * @public + */ +export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise { + const actualTx = extractTx(tx) + if (actualTx._class !== core.class.TxCreateDoc) { + return [] + } + + const createTx = tx as TxCreateDoc + + if (!control.hierarchy.isDerived(createTx.objectClass, gmail.class.Message)) { + return [] + } + const message = TxProcessor.createDoc2Doc(createTx) + + const channel = (await control.findAll(contact.class.Channel, { _id: message.attachedTo }, { limit: 1 }))[0] + if (channel === undefined) { + return [] + } + if (channel.lastMessage === undefined || channel.lastMessage < message.sendOn) { + const tx = control.txFactory.createTxUpdateDoc(channel._class, channel.space, channel._id, { + lastMessage: message.sendOn + }) + return [tx] + } + return [] +} + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export default async () => ({ + trigger: { + OnMessageCreate + }, function: { FindMessages } diff --git a/server-plugins/gmail/package.json b/server-plugins/gmail/package.json index 10aabcafd6..8ed4fa844b 100644 --- a/server-plugins/gmail/package.json +++ b/server-plugins/gmail/package.json @@ -28,6 +28,7 @@ }, "dependencies": { "@anticrm/core": "~0.6.16", - "@anticrm/platform": "~0.6.6" + "@anticrm/platform": "~0.6.6", + "@anticrm/server-core": "~0.6.1" } } diff --git a/server-plugins/gmail/src/index.ts b/server-plugins/gmail/src/index.ts index 4dc6614d19..d5c48eea83 100644 --- a/server-plugins/gmail/src/index.ts +++ b/server-plugins/gmail/src/index.ts @@ -17,6 +17,7 @@ import { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref } from '@anticrm/core' import type { Plugin, Resource } from '@anticrm/platform' import { plugin } from '@anticrm/platform' +import { TriggerFunc } from '@anticrm/server-core' /** * @public @@ -27,6 +28,9 @@ export const serverGmailId = 'server-gmail' as Plugin * @public */ export default plugin(serverGmailId, { + trigger: { + OnMessageCreate: '' as Resource + }, function: { FindMessages: '' as Resource< ( diff --git a/server-plugins/inventory-resources/src/index.ts b/server-plugins/inventory-resources/src/index.ts index c9abd3ef20..0f3993372f 100644 --- a/server-plugins/inventory-resources/src/index.ts +++ b/server-plugins/inventory-resources/src/index.ts @@ -32,7 +32,7 @@ const extractTx = (tx: Tx): Tx => { create.attributes.collection = ctx.collection return create } - return ctx + return ctx.tx } return tx diff --git a/server-plugins/telegram-resources/src/index.ts b/server-plugins/telegram-resources/src/index.ts index 70c6f65557..fc94d3d439 100644 --- a/server-plugins/telegram-resources/src/index.ts +++ b/server-plugins/telegram-resources/src/index.ts @@ -14,8 +14,38 @@ // import contact, { Channel } from '@anticrm/contact' -import telegram from '@anticrm/telegram' -import { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref } from '@anticrm/core' +import core, { + AttachedDoc, + Class, + Doc, + DocumentQuery, + FindOptions, + FindResult, + Hierarchy, + Ref, + Tx, + TxCollectionCUD, + TxCreateDoc, + TxProcessor +} from '@anticrm/core' +import { TriggerControl } from '@anticrm/server-core' +import telegram, { TelegramMessage } from '@anticrm/telegram' + +const extractTx = (tx: Tx): Tx => { + if (tx._class === core.class.TxCollectionCUD) { + const ctx = tx as TxCollectionCUD + if (ctx.tx._class === core.class.TxCreateDoc) { + const create = ctx.tx as TxCreateDoc + create.attributes.attachedTo = ctx.objectId + create.attributes.attachedToClass = ctx.objectClass + create.attributes.collection = ctx.collection + return create + } + return ctx.tx + } + + return tx +} /** * @public @@ -33,13 +63,45 @@ export async function FindMessages ( if (channel.provider !== contact.channelProvider.Telegram) { return [] } - const messages = await findAll(telegram.class.Message, { attachedTo: doc._id }) - const newMessages = await findAll(telegram.class.NewMessage, { attachedTo: doc._id }) + const messages = await findAll(telegram.class.Message, { attachedTo: channel._id }) + const newMessages = await findAll(telegram.class.NewMessage, { attachedTo: channel._id }) return [...messages, ...newMessages] } +/** + * @public + */ +export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise { + const actualTx = extractTx(tx) + if (actualTx._class !== core.class.TxCreateDoc) { + return [] + } + + const createTx = tx as TxCreateDoc + + if (!control.hierarchy.isDerived(createTx.objectClass, telegram.class.Message)) { + return [] + } + const message = TxProcessor.createDoc2Doc(createTx) + + const channel = (await control.findAll(contact.class.Channel, { _id: message.attachedTo }, { limit: 1 }))[0] + if (channel === undefined) { + return [] + } + if (channel.lastMessage === undefined || channel.lastMessage < message.sendOn) { + const tx = control.txFactory.createTxUpdateDoc(channel._class, channel.space, channel._id, { + lastMessage: message.sendOn + }) + return [tx] + } + return [] +} + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export default async () => ({ + trigger: { + OnMessageCreate + }, function: { FindMessages } diff --git a/server-plugins/telegram/package.json b/server-plugins/telegram/package.json index 932ff9a277..a4f52264e3 100644 --- a/server-plugins/telegram/package.json +++ b/server-plugins/telegram/package.json @@ -28,6 +28,7 @@ }, "dependencies": { "@anticrm/core": "~0.6.16", - "@anticrm/platform": "~0.6.6" + "@anticrm/platform": "~0.6.6", + "@anticrm/server-core": "~0.6.1" } } diff --git a/server-plugins/telegram/src/index.ts b/server-plugins/telegram/src/index.ts index 7323cbde31..5f72ebb27e 100644 --- a/server-plugins/telegram/src/index.ts +++ b/server-plugins/telegram/src/index.ts @@ -17,6 +17,7 @@ import { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref } from '@anticrm/core' import type { Plugin, Resource } from '@anticrm/platform' import { plugin } from '@anticrm/platform' +import { TriggerFunc } from '@anticrm/server-core' /** * @public @@ -27,6 +28,9 @@ export const serverTelegramId = 'server-telegram' as Plugin * @public */ export default plugin(serverTelegramId, { + trigger: { + OnMessageCreate: '' as Resource + }, function: { FindMessages: '' as Resource< (