diff --git a/dev/tool/package.json b/dev/tool/package.json index e110f3a966..e4a8b48737 100644 --- a/dev/tool/package.json +++ b/dev/tool/package.json @@ -61,6 +61,7 @@ "@anticrm/server-core": "~0.6.1", "@anticrm/server-token": "~0.6.0", "@anticrm/model-attachment": "~0.6.0", + "@anticrm/model-contact": "~0.6.0", "@anticrm/mongo": "~0.6.0", "@anticrm/dev-storage": "~0.6.0", "@anticrm/server-attachment": "~0.6.0", diff --git a/dev/tool/src/index.ts b/dev/tool/src/index.ts index 03cc68d574..334b47e8d4 100644 --- a/dev/tool/src/index.ts +++ b/dev/tool/src/index.ts @@ -199,9 +199,25 @@ program }) program - .command('clear-telegram-history') + .command('clear-telegram-history <workspace>') .description('clear telegram history') .option('-w, --workspace <workspace>', 'target workspace') + .action(async (workspace: string, cmd) => { + return await withDatabase(mongodbUri, async (db) => { + const telegramDB = process.env.TELEGRAM_DATABASE + if (telegramDB === undefined) { + console.error('please provide TELEGRAM_DATABASE.') + process.exit(1) + } + + console.log(`clearing ${workspace} history:`) + await clearTelegramHistory(mongodbUri, workspace, telegramDB, minio) + }) + }) + +program + .command('clear-telegram-all-history') + .description('clear telegram history') .action(async (cmd) => { return await withDatabase(mongodbUri, async (db) => { const telegramDB = process.env.TELEGRAM_DATABASE @@ -211,12 +227,10 @@ program } const workspaces = await listWorkspaces(db) - const targetWorkspaces = - cmd.workspace !== undefined ? workspaces.filter((x) => x.workspace === cmd.workspace) : workspaces - for (const w of targetWorkspaces) { + for (const w of workspaces) { console.log(`clearing ${w.workspace} history:`) - await clearTelegramHistory(mongodbUri, w.workspace, telegramDB) + await clearTelegramHistory(mongodbUri, w.workspace, telegramDB, minio) } }) }) diff --git a/dev/tool/src/telegram.ts b/dev/tool/src/telegram.ts index 4ae7f8c04f..c7491da144 100644 --- a/dev/tool/src/telegram.ts +++ b/dev/tool/src/telegram.ts @@ -14,24 +14,60 @@ // limitations under the License. // -import { MongoClient } from 'mongodb' - -import { DOMAIN_TX } from '@anticrm/core' +import { DOMAIN_TX, Ref } from '@anticrm/core' +import { DOMAIN_ATTACHMENT } from '@anticrm/model-attachment' +import contact, { DOMAIN_CHANNEL } from '@anticrm/model-contact' import { DOMAIN_TELEGRAM } from '@anticrm/model-telegram' -import telegram from '@anticrm/telegram' +import telegram, { SharedTelegramMessage, SharedTelegramMessages } from '@anticrm/telegram' +import { Client } from 'minio' +import { MongoClient } from 'mongodb' const LastMessages = 'last-msgs' /** * @public */ -export async function clearTelegramHistory (mongoUrl: string, workspace: string, tgDb: string): Promise<void> { +export async function clearTelegramHistory ( + mongoUrl: string, + workspace: string, + tgDb: string, + minio: Client +): Promise<void> { const client = new MongoClient(mongoUrl) try { await client.connect() const workspaceDB = client.db(workspace) const telegramDB = client.db(tgDb) + const sharedMessages = await workspaceDB + .collection(DOMAIN_TELEGRAM) + .find<SharedTelegramMessages>({ + _class: telegram.class.SharedMessages + }) + .toArray() + const sharedIds: Ref<SharedTelegramMessage>[] = [] + for (const sharedMessage of sharedMessages) { + for (const message of sharedMessage.messages) { + sharedIds.push(message._id) + } + } + const files = await workspaceDB + .collection(DOMAIN_ATTACHMENT) + .find( + { + attachedToClass: telegram.class.Message, + attachedTo: { $nin: sharedIds } + }, + { + projection: { + file: 1 + } + } + ) + .toArray() + + const attachments = files.map((file) => file.file) + console.log('clearing txes and messages...') await Promise.all([ workspaceDB.collection(DOMAIN_TX).deleteMany({ @@ -39,7 +75,21 @@ export async function clearTelegramHistory (mongoUrl: string, workspace: string, }), workspaceDB.collection(DOMAIN_TELEGRAM).deleteMany({ _class: telegram.class.Message - }) + }), + workspaceDB.collection(DOMAIN_CHANNEL).updateMany( + { + provider: contact.channelProvider.Telegram + }, + { + $set: { + items: 0 + } + } + ), + workspaceDB.collection(DOMAIN_ATTACHMENT).deleteMany({ + attachedToClass: telegram.class.Message + }), + minio.removeObjects(workspace, Array.from(attachments)) ]) console.log('clearing telegram service data...') diff --git a/models/telegram/package.json b/models/telegram/package.json index a38a45ad2c..3524e4fb29 100644 --- a/models/telegram/package.json +++ b/models/telegram/package.json @@ -30,6 +30,7 @@ "@anticrm/core": "~0.6.0", "@anticrm/platform": "~0.6.5", "@anticrm/model-core": "~0.6.0", + "@anticrm/model-attachment": "~0.6.0", "@anticrm/model-contact": "~0.6.1", "@anticrm/telegram": "~0.6.0", "@anticrm/telegram-resources": "~0.6.0", diff --git a/models/telegram/src/index.ts b/models/telegram/src/index.ts index 15fd040e70..db2413c18e 100644 --- a/models/telegram/src/index.ts +++ b/models/telegram/src/index.ts @@ -14,14 +14,20 @@ // limitations under the License. // -import { Builder, Model, TypeString, TypeBoolean, Prop, ArrOf, Index } from '@anticrm/model' +import { Builder, Model, TypeString, TypeBoolean, Prop, ArrOf, Index, Collection } from '@anticrm/model' import core, { TAttachedDoc } from '@anticrm/model-core' import contact from '@anticrm/model-contact' import telegram from './plugin' -import type { TelegramMessage, SharedTelegramMessage, SharedTelegramMessages } from '@anticrm/telegram' +import type { + TelegramMessage, + NewTelegramMessage, + SharedTelegramMessage, + SharedTelegramMessages +} from '@anticrm/telegram' import { Domain, IndexKind, Type } from '@anticrm/core' import setting from '@anticrm/setting' import activity from '@anticrm/activity' +import attachment from '@anticrm/model-attachment' export const DOMAIN_TELEGRAM = 'telegram' as Domain @@ -37,6 +43,22 @@ export class TTelegramMessage extends TAttachedDoc implements TelegramMessage { @Prop(TypeBoolean(), telegram.string.Incoming) incoming!: boolean + + @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments) + attachments?: number +} + +@Model(telegram.class.NewMessage, core.class.AttachedDoc, DOMAIN_TELEGRAM) +export class TNewTelegramMessage extends TAttachedDoc implements NewTelegramMessage { + @Prop(TypeString(), telegram.string.Content) + @Index(IndexKind.FullText) + content!: string + + @Prop(TypeString(), telegram.string.Status) + status!: 'new' | 'sent' + + @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments) + attachments?: number } @Model(telegram.class.SharedMessages, core.class.AttachedDoc, DOMAIN_TELEGRAM) @@ -46,7 +68,7 @@ export class TSharedTelegramMessages extends TAttachedDoc implements SharedTeleg } export function createModel (builder: Builder): void { - builder.createModel(TTelegramMessage, TSharedTelegramMessages) + builder.createModel(TTelegramMessage, TSharedTelegramMessages, TNewTelegramMessage) builder.createDoc( contact.class.ChannelProvider, diff --git a/models/telegram/src/plugin.ts b/models/telegram/src/plugin.ts index e459c3efac..bfb218b8f2 100644 --- a/models/telegram/src/plugin.ts +++ b/models/telegram/src/plugin.ts @@ -29,7 +29,8 @@ export default mergeIds(telegramId, telegram, { Incoming: '' as IntlString, Messages: '' as IntlString, Telegram: '' as IntlString, - TelegramIntegrationDesc: '' as IntlString + TelegramIntegrationDesc: '' as IntlString, + Status: '' as IntlString }, ids: { TxSharedCreate: '' as Ref<TxViewlet> diff --git a/plugins/telegram-assets/lang/en.json b/plugins/telegram-assets/lang/en.json index a70b20bc90..bdaa28a3ab 100644 --- a/plugins/telegram-assets/lang/en.json +++ b/plugins/telegram-assets/lang/en.json @@ -20,6 +20,7 @@ "Incoming": "Incoming", "Messages": "Messages", "Telegram": "Telegram", - "TelegramIntegrationDesc": "Use telegram integration" + "TelegramIntegrationDesc": "Use telegram integration", + "Status": "Status" } } \ No newline at end of file diff --git a/plugins/telegram-assets/lang/ru.json b/plugins/telegram-assets/lang/ru.json index 462ce67f97..5ac27ef965 100644 --- a/plugins/telegram-assets/lang/ru.json +++ b/plugins/telegram-assets/lang/ru.json @@ -20,6 +20,7 @@ "Incoming": "Входящее", "Messages": "Сообщения", "Telegram": "Telegram", - "TelegramIntegrationDesc": "Подключить Telegram" + "TelegramIntegrationDesc": "Подключить Telegram", + "Status": "Статус" } } \ No newline at end of file diff --git a/plugins/telegram-resources/package.json b/plugins/telegram-resources/package.json index c642d86057..542edccf27 100644 --- a/plugins/telegram-resources/package.json +++ b/plugins/telegram-resources/package.json @@ -41,6 +41,8 @@ "@anticrm/chunter": "~0.6.0", "@anticrm/login": "~0.6.1", "@anticrm/core": "~0.6.11", - "@anticrm/notification-resources": "~0.6.0" + "@anticrm/notification-resources": "~0.6.0", + "@anticrm/attachment": "~0.6.0", + "@anticrm/attachment-resources": "~0.6.0" } } diff --git a/plugins/telegram-resources/src/components/Chat.svelte b/plugins/telegram-resources/src/components/Chat.svelte index 80c0166d5a..3f9484dae6 100644 --- a/plugins/telegram-resources/src/components/Chat.svelte +++ b/plugins/telegram-resources/src/components/Chat.svelte @@ -14,23 +14,24 @@ // limitations under the License. --> <script lang="ts"> + import attachment from '@anticrm/attachment' + import { AttachmentRefInput } from '@anticrm/attachment-resources' import contact, { Channel, Contact, EmployeeAccount, formatName } from '@anticrm/contact' - import { getCurrentAccount, Ref, SortingOrder, Space } from '@anticrm/core' - import login from '@anticrm/login' - import { getMetadata } from '@anticrm/platform' + import { generateId, getCurrentAccount, Ref, SortingOrder, Space } from '@anticrm/core' + import { NotificationClientImpl } from '@anticrm/notification-resources' import { createQuery, getClient } from '@anticrm/presentation' import setting from '@anticrm/setting' - import type { SharedTelegramMessage, TelegramMessage } from '@anticrm/telegram' - import { ReferenceInput } from '@anticrm/text-editor' + import type { NewTelegramMessage, SharedTelegramMessage, TelegramMessage } from '@anticrm/telegram' import { ActionIcon, Button, IconShare, ScrollBox, showPopup } from '@anticrm/ui' import telegram from '../plugin' import Connect from './Connect.svelte' import TelegramIcon from './icons/Telegram.svelte' import Messages from './Messages.svelte' - import { NotificationClientImpl } from '@anticrm/notification-resources' export let object: Contact let channel: Channel | undefined = undefined + let objectId: Ref<NewTelegramMessage> = generateId() + const client = getClient() const notificationClient = NotificationClientImpl.getClient() @@ -48,7 +49,6 @@ let enabled: boolean let selected: Set<Ref<SharedTelegramMessage>> = new Set<Ref<SharedTelegramMessage>>() let selectable = false - const url = getMetadata(login.metadata.TelegramUrl) ?? '' const messagesQuery = createQuery() const accauntsQuery = createQuery() @@ -67,7 +67,13 @@ const accountsIds = new Set(messages.map((p) => p.modifiedBy as Ref<EmployeeAccount>)) updateAccountsQuery(accountsIds) }, - { sort: { modifiedOn: SortingOrder.Descending }, limit: 500 } + { + sort: { modifiedOn: SortingOrder.Descending }, + limit: 500, + lookup: { + _id: { attachments: attachment.class.Attachment } + } + } ) } @@ -87,63 +93,24 @@ } ) - async function sendMsg (to: string, msg: string) { - const res = await fetch(url + '/send-msg', { - method: 'POST', - headers: { - Authorization: 'Bearer ' + getMetadata(login.metadata.LoginToken), - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - to, - msg - }) - }) - if (channel !== undefined) { - await notificationClient.updateLastView(channel._id, channel._class, undefined, true) - } - return res - } - - async function addContact (phone: string) { - const [lastName, firstName] = object.name.split(',') - - return await fetch(url + '/add-contact', { - method: 'POST', - headers: { - Authorization: 'Bearer ' + getMetadata(login.metadata.LoginToken), - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - firstName: firstName ?? '', - lastName: lastName ?? '', - phone - }) - }) - } - async function onMessage (event: CustomEvent) { - const to = channel?.value ?? '' - const sendRes = await sendMsg(to, event.detail) + if (channel === undefined) return + const { message, attachments } = event.detail + await client.createDoc( + telegram.class.NewMessage, + telegram.space.Telegram, + { + content: message, + status: 'new', + attachments, + attachedTo: channel._id, + attachedToClass: channel._class, + collection: 'newMessages' + }, + objectId + ) - if (sendRes.status !== 400 || !to.startsWith('+')) { - return - } - - const err = await sendRes.json() - if (err.code !== 'CONTACT_IMPORT_REQUIRED') { - return - } - - const addRes = await addContact(to) - - if (Math.trunc(addRes.status / 100) !== 2) { - const { message } = await addRes.json().catch(() => ({ message: 'Unknown error' })) - - throw Error(message) - } - - await sendMsg(to, event.detail) + objectId = generateId() } function getName (message: TelegramMessage, accounts: EmployeeAccount[]): string { @@ -240,7 +207,12 @@ </div> </div> {:else if enabled} - <ReferenceInput on:message={onMessage} /> + <AttachmentRefInput + space={telegram.space.Telegram} + _class={telegram.class.NewMessage} + {objectId} + on:message={onMessage} + /> {:else} <div class="flex-center"> <Button diff --git a/plugins/telegram-resources/src/components/Message.svelte b/plugins/telegram-resources/src/components/Message.svelte index fe20371f70..6265594f21 100644 --- a/plugins/telegram-resources/src/components/Message.svelte +++ b/plugins/telegram-resources/src/components/Message.svelte @@ -14,17 +14,20 @@ // limitations under the License. --> <script lang="ts"> - import type { SharedTelegramMessage } from '@anticrm/telegram' - import { MessageViewer } from '@anticrm/presentation' + import { Attachment } from '@anticrm/attachment' + import { AttachmentList } from '@anticrm/attachment-resources' import { formatName } from '@anticrm/contact' - import { CheckBox } from '@anticrm/ui' + import { WithLookup } from '@anticrm/core' + import { MessageViewer } from '@anticrm/presentation' + import type { SharedTelegramMessage } from '@anticrm/telegram' + import { CheckBox, getPlatformColorForText } from '@anticrm/ui' import { createEventDispatcher } from 'svelte' - import { getPlatformColorForText } from '@anticrm/ui' - export let message: SharedTelegramMessage + export let message: WithLookup<SharedTelegramMessage> export let showName: boolean = false export let selected: boolean = false export let selectable: boolean = false + $: attachments = message.$lookup?.attachments ? (message.$lookup.attachments as Attachment[]) : undefined const dispatch = createEventDispatcher() </script> @@ -32,7 +35,7 @@ <div class="flex-between" class:selectable - on:click|preventDefault={() => { + on:click={() => { dispatch('select', message) }} > @@ -41,6 +44,9 @@ {#if showName} <div class="name" style="color: {getPlatformColorForText(message.sender)}">{formatName(message.sender)}</div> {/if} + {#if attachments} + <AttachmentList {attachments} /> + {/if} <div class="flex"> <div class="caption-color mr-4"><MessageViewer message={message.content} /></div> <div class="time"> diff --git a/plugins/telegram/src/index.ts b/plugins/telegram/src/index.ts index 2b0303d336..e6bee05d3f 100644 --- a/plugins/telegram/src/index.ts +++ b/plugins/telegram/src/index.ts @@ -22,16 +22,29 @@ import type { IntegrationType, Handler } from '@anticrm/setting' /** * @public */ -export interface TelegramMessage extends AttachedDoc { +export interface BaseTelegramMessage extends Doc { content: string + attachments?: number +} + +/** + * @public + */ +export interface TelegramMessage extends BaseTelegramMessage, AttachedDoc { incoming: boolean } /** * @public */ -export interface SharedTelegramMessage extends Doc { - content: string +export interface NewTelegramMessage extends BaseTelegramMessage, AttachedDoc { + status: 'new' | 'sent' +} + +/** + * @public + */ +export interface SharedTelegramMessage extends BaseTelegramMessage { incoming: boolean sender: string } @@ -62,6 +75,7 @@ export default plugin(telegramId, { }, class: { Message: '' as Ref<Class<TelegramMessage>>, + NewMessage: '' as Ref<Class<NewTelegramMessage>>, SharedMessage: '' as Ref<Class<SharedTelegramMessage>>, SharedMessages: '' as Ref<Class<SharedTelegramMessages>> },