mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Init directs (#95)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
@@ -45,7 +45,8 @@ import {
|
||||
type BlobID,
|
||||
type AttachmentData,
|
||||
type AttachmentID,
|
||||
type AttachmentUpdateData, WithTotal
|
||||
type AttachmentUpdateData, WithTotal, PeerKind, PeerExtra,
|
||||
FindPeersParams, Peer, FindThreadParams
|
||||
} from '@hcengineering/communication-types'
|
||||
import type {
|
||||
DbAdapter,
|
||||
@@ -66,11 +67,13 @@ import { formatName } from './utils'
|
||||
import { initSchema } from './init'
|
||||
import { LabelsDb } from './db/label'
|
||||
import { SqlClient } from './client'
|
||||
import { PeersDb } from './db/peer'
|
||||
|
||||
export class CockroachAdapter implements DbAdapter {
|
||||
private readonly message: MessagesDb
|
||||
private readonly notification: NotificationsDb
|
||||
private readonly label: LabelsDb
|
||||
private readonly peer: PeersDb
|
||||
|
||||
constructor (
|
||||
private readonly sql: SqlClient,
|
||||
@@ -81,6 +84,7 @@ export class CockroachAdapter implements DbAdapter {
|
||||
this.message = new MessagesDb(this.sql, this.workspace, logger, options)
|
||||
this.notification = new NotificationsDb(this.sql, this.workspace, logger, options)
|
||||
this.label = new LabelsDb(this.sql, this.workspace, logger, options)
|
||||
this.peer = new PeersDb(this.sql, this.workspace, logger, options)
|
||||
}
|
||||
|
||||
async createMessage (
|
||||
@@ -201,8 +205,8 @@ export class CockroachAdapter implements DbAdapter {
|
||||
return await this.message.findMessagesGroups(params)
|
||||
}
|
||||
|
||||
async findThread (thread: CardID): Promise<Thread | undefined> {
|
||||
return await this.message.findThread(thread)
|
||||
async findThreads (params: FindThreadParams): Promise<Thread[]> {
|
||||
return await this.message.findThreads(params)
|
||||
}
|
||||
|
||||
async addCollaborators (
|
||||
@@ -316,6 +320,28 @@ export class CockroachAdapter implements DbAdapter {
|
||||
return this.label.updateLabels(card, updates)
|
||||
}
|
||||
|
||||
async createPeer (
|
||||
workspaceId: WorkspaceID,
|
||||
cardId: CardID,
|
||||
kind: PeerKind,
|
||||
value: string,
|
||||
extra: PeerExtra,
|
||||
date: Date
|
||||
): Promise<void> {
|
||||
await this.peer.createPeer(workspaceId, cardId, kind, value, extra, date)
|
||||
}
|
||||
|
||||
async removePeer (workspaceId: WorkspaceID,
|
||||
cardId: CardID,
|
||||
kind: PeerKind,
|
||||
value: string): Promise<void> {
|
||||
await this.peer.removePeer(workspaceId, cardId, kind, value)
|
||||
}
|
||||
|
||||
findPeers (params: FindPeersParams): Promise<Peer[]> {
|
||||
return this.peer.findPeers(params)
|
||||
}
|
||||
|
||||
async getAccountsByPersonIds (ids: string[]): Promise<AccountID[]> {
|
||||
if (ids.length === 0) return []
|
||||
const sql = `SELECT data ->> 'personUuid' AS "personUuid"
|
||||
@@ -350,6 +376,19 @@ export class CockroachAdapter implements DbAdapter {
|
||||
return result[0]?.title
|
||||
}
|
||||
|
||||
// TODO: remove later
|
||||
async getCardSpaceMembers (cardId: CardID): Promise<AccountID[]> {
|
||||
const sql = `SELECT s.members
|
||||
FROM public.space AS s
|
||||
JOIN public.card AS c ON c.space = s._id
|
||||
WHERE c."workspaceId" = $1::uuid
|
||||
AND c."_id" = $2::text
|
||||
LIMIT 1`
|
||||
|
||||
const result = await this.sql.execute(sql, [this.workspace, cardId])
|
||||
return result[0]?.members ?? []
|
||||
}
|
||||
|
||||
async getMessageCreated (cardId: CardID, messageId: MessageID): Promise<Date | undefined> {
|
||||
return await this.message.getMessageCreated(cardId, messageId)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,10 @@ import {
|
||||
type AccountID,
|
||||
type MessageExtra,
|
||||
AttachmentID,
|
||||
Attachment
|
||||
Attachment,
|
||||
Peer,
|
||||
WorkspaceID,
|
||||
PeerExtra
|
||||
} from '@hcengineering/communication-types'
|
||||
import { Domain } from '@hcengineering/communication-sdk-types'
|
||||
import { applyPatches } from '@hcengineering/communication-shared'
|
||||
@@ -296,3 +299,31 @@ export function toLabel (raw: DbModel<Domain.Label>): Label {
|
||||
created: new Date(raw.created)
|
||||
}
|
||||
}
|
||||
|
||||
export function toPeer (
|
||||
raw: DbModel<Domain.Peer> & { members?: { workspace_id: WorkspaceID, card_id: CardID, extra?: PeerExtra }[] }
|
||||
): Peer {
|
||||
const peer: Peer = {
|
||||
workspaceId: raw.workspace_id,
|
||||
cardId: raw.card_id,
|
||||
kind: raw.kind,
|
||||
value: raw.value,
|
||||
extra: raw.extra,
|
||||
created: new Date(raw.created)
|
||||
}
|
||||
|
||||
if (peer.kind === 'card') {
|
||||
return {
|
||||
...peer,
|
||||
kind: 'card',
|
||||
members:
|
||||
raw.members?.map((it) => ({
|
||||
workspaceId: it.workspace_id,
|
||||
cardId: it.card_id,
|
||||
extra: it.extra ?? {}
|
||||
})) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
return peer
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
type CardType,
|
||||
type FindMessagesGroupsParams,
|
||||
type FindMessagesParams,
|
||||
FindThreadParams,
|
||||
type Markdown,
|
||||
type Message,
|
||||
type MessageExtra,
|
||||
@@ -753,21 +754,52 @@ export class MessagesDb extends BaseDb {
|
||||
return { where: `WHERE ${where.join(' AND ')}`, values }
|
||||
}
|
||||
|
||||
// Find thread
|
||||
async findThread (thread: CardID): Promise<Thread | undefined> {
|
||||
const sql = `SELECT t.card_id,
|
||||
t.message_id::text,
|
||||
t.thread_id,
|
||||
t.thread_type,
|
||||
t.replies_count::int,
|
||||
t.last_reply
|
||||
FROM ${Domain.Thread} t
|
||||
WHERE t.workspace_id = $1::uuid
|
||||
AND t.thread_id = $2::varchar
|
||||
LIMIT 1;`
|
||||
// Find threads
|
||||
async findThreads (params: FindThreadParams): Promise<Thread[]> {
|
||||
const { where, values } = this.buildThreadWhere(params)
|
||||
const select = `
|
||||
SELECT *
|
||||
FROM ${Domain.Thread} t
|
||||
`
|
||||
|
||||
const result = await this.execute(sql, [this.workspace, thread], 'find thread')
|
||||
return result.map((it: any) => toThread(it))[0]
|
||||
const limit = params.limit != null ? ` LIMIT ${params.limit}` : ''
|
||||
const orderBy =
|
||||
params.order != null ? `ORDER BY t.date ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}` : ''
|
||||
|
||||
const sql = [select, where, orderBy, limit].join(' ')
|
||||
const result = await this.execute(sql, values, 'find threads')
|
||||
|
||||
return result.map((it: any) => toThread(it))
|
||||
}
|
||||
|
||||
private buildThreadWhere (
|
||||
params: FindThreadParams,
|
||||
startIndex: number = 0,
|
||||
prefix: string = 't.'
|
||||
): { where: string, values: any[] } {
|
||||
const where: string[] = []
|
||||
const values: any[] = []
|
||||
let index = startIndex + 1
|
||||
|
||||
where.push(`${prefix}workspace_id = $${index++}::uuid`)
|
||||
values.push(this.workspace)
|
||||
|
||||
if (params.cardId != null) {
|
||||
where.push(`${prefix}card_id = $${index++}::varchar`)
|
||||
values.push(params.cardId)
|
||||
}
|
||||
|
||||
if (params.messageId != null) {
|
||||
where.push(`${prefix}message_id = $${index++}::varchar`)
|
||||
values.push(params.messageId)
|
||||
}
|
||||
|
||||
if (params.threadId != null) {
|
||||
where.push(`${prefix}thread_id = $${index++}::varchar`)
|
||||
values.push(params.threadId)
|
||||
}
|
||||
|
||||
return { where: `WHERE ${where.join(' AND ')}`, values }
|
||||
}
|
||||
|
||||
// Find messages groups
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
//
|
||||
// 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 {
|
||||
WorkspaceID,
|
||||
type CardID,
|
||||
PeerKind,
|
||||
PeerExtra,
|
||||
FindPeersParams,
|
||||
SortingOrder,
|
||||
Peer
|
||||
} from '@hcengineering/communication-types'
|
||||
import { Domain } from '@hcengineering/communication-sdk-types'
|
||||
|
||||
import { BaseDb } from './base'
|
||||
import { DbModel, DbModelFilter } from '../schema'
|
||||
import { toPeer } from './mapping'
|
||||
|
||||
export class PeersDb extends BaseDb {
|
||||
async createPeer (
|
||||
workspaceId: WorkspaceID,
|
||||
cardId: CardID,
|
||||
kind: PeerKind,
|
||||
value: string,
|
||||
extra: PeerExtra,
|
||||
date: Date
|
||||
): Promise<void> {
|
||||
const db: DbModel<Domain.Peer> = {
|
||||
workspace_id: workspaceId,
|
||||
card_id: cardId,
|
||||
kind,
|
||||
value,
|
||||
extra,
|
||||
created: date
|
||||
}
|
||||
const { sql, values } = this.getInsertSql(Domain.Peer, db, [])
|
||||
await this.execute(sql, values, 'insert peer')
|
||||
}
|
||||
|
||||
async removePeer (workspaceId: WorkspaceID, cardId: CardID, kind: PeerKind, value: string): Promise<void> {
|
||||
const filter: DbModelFilter<Domain.Peer> = [
|
||||
{
|
||||
column: 'workspace_id',
|
||||
value: workspaceId
|
||||
},
|
||||
{
|
||||
column: 'card_id',
|
||||
value: cardId
|
||||
},
|
||||
{
|
||||
column: 'kind',
|
||||
value: kind
|
||||
},
|
||||
{
|
||||
column: 'value',
|
||||
value
|
||||
}
|
||||
]
|
||||
|
||||
if (filter.length === 0) return
|
||||
|
||||
const { sql, values } = this.getDeleteSql(Domain.Peer, filter)
|
||||
|
||||
await this.execute(sql, values, 'remove peer')
|
||||
}
|
||||
|
||||
async findPeers (params: FindPeersParams): Promise<Peer[]> {
|
||||
const select = `SELECT *, COALESCE(members.members, '[]') AS members
|
||||
FROM ${Domain.Peer} p`
|
||||
|
||||
const { where, values } = this.buildWhere(params)
|
||||
|
||||
const limit = params.limit != null ? `LIMIT ${params.limit}` : ''
|
||||
const orderBy =
|
||||
params.order != null ? `ORDER BY p.created ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}` : ''
|
||||
const join = `LEFT JOIN LATERAL (
|
||||
SELECT json_agg(
|
||||
json_build_object(
|
||||
'workspace_id', p2.workspace_id,
|
||||
'card_id', p2.card_id,
|
||||
'extra', p2.extra
|
||||
)
|
||||
) AS members
|
||||
FROM ${Domain.Peer} AS p2
|
||||
WHERE p2.value = p.value
|
||||
AND p2.kind = 'card'
|
||||
AND NOT (p2.workspace_id = p.workspace_id AND p2.card_id = p.card_id)
|
||||
) members ON true`
|
||||
const sql = [select, join, where, orderBy, limit].join(' ')
|
||||
|
||||
const result = await this.execute(sql, values, 'find peers')
|
||||
|
||||
return result.map((it: any) => toPeer(it))
|
||||
}
|
||||
|
||||
buildWhere (params: FindPeersParams, startIndex: number = 0, prefix = 'p.'): { where: string, values: any[] } {
|
||||
const where: string[] = []
|
||||
const values: any[] = []
|
||||
let index = startIndex + 1
|
||||
|
||||
if (params.workspaceId != null) {
|
||||
where.push(`${prefix}workspace_id = $${index++}::uuid`)
|
||||
values.push(params.workspaceId)
|
||||
}
|
||||
|
||||
if (params.cardId != null) {
|
||||
where.push(`${prefix}card_id = $${index++}::varchar`)
|
||||
values.push(params.cardId)
|
||||
}
|
||||
|
||||
if (params.kind != null) {
|
||||
where.push(`${prefix}kind = $${index++}::varchar`)
|
||||
values.push(params.kind)
|
||||
}
|
||||
|
||||
if (params.value != null) {
|
||||
where.push(`${prefix}value = $${index++}::varchar`)
|
||||
values.push(params.value)
|
||||
}
|
||||
|
||||
return { where: where.length > 0 ? `WHERE ${where.join(' AND ')}` : '', values }
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,8 @@ function getMigrations (): [string, string][] {
|
||||
migrationV7_3(),
|
||||
migrationV8_1(),
|
||||
migrationV8_2(),
|
||||
migrationV8_3()
|
||||
migrationV8_3(),
|
||||
migrationV9_1()
|
||||
]
|
||||
}
|
||||
|
||||
@@ -497,7 +498,8 @@ function migrationV6_7 (): [string, string] {
|
||||
CREATE INDEX IF NOT EXISTS idx_reactions_workspace_card_message
|
||||
ON communication.reactions (workspace_id, card_id, message_id);
|
||||
|
||||
ALTER TABLE communication.thread ADD CONSTRAINT thread_unique_constraint UNIQUE (workspace_id, card_id, message_id);
|
||||
ALTER TABLE communication.thread
|
||||
ADD CONSTRAINT thread_unique_constraint UNIQUE (workspace_id, card_id, message_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_thread_workspace_card_message
|
||||
ON communication.thread (workspace_id, card_id, message_id);
|
||||
@@ -541,9 +543,8 @@ function migrationV7_2 (): [string, string] {
|
||||
FROM communication.notification_context AS nc
|
||||
JOIN communication.messages_groups AS mg
|
||||
ON mg.workspace_id = nc.workspace_id
|
||||
AND mg.card_id = nc.card_id
|
||||
WHERE
|
||||
n.context_id = nc.id
|
||||
AND mg.card_id = nc.card_id
|
||||
WHERE n.context_id = nc.id
|
||||
AND n.message_created BETWEEN mg.from_date AND mg.to_date
|
||||
AND n.blob_id IS NULL;
|
||||
`
|
||||
@@ -552,12 +553,12 @@ function migrationV7_2 (): [string, string] {
|
||||
|
||||
function migrationV7_3 (): [string, string] {
|
||||
const sql = `
|
||||
UPDATE communication.notification_context
|
||||
SET last_notify = last_update
|
||||
WHERE last_notify IS NULL;
|
||||
UPDATE communication.notification_context
|
||||
SET last_notify = last_update
|
||||
WHERE last_notify IS NULL;
|
||||
|
||||
ALTER TABLE communication.notification_context
|
||||
ALTER COLUMN last_notify SET NOT NULL;
|
||||
ALTER TABLE communication.notification_context
|
||||
ALTER COLUMN last_notify SET NOT NULL;
|
||||
`
|
||||
return ['make_last_notify_not_null-v7_3', sql]
|
||||
}
|
||||
@@ -600,7 +601,34 @@ function migrationV8_2 (): [string, string] {
|
||||
|
||||
function migrationV8_3 (): [string, string] {
|
||||
const sql = `
|
||||
CREATE INDEX IF NOT EXISTS attachment_workspace_card_message_idx ON ${Domain.Attachment} (workspace_id, card_id, message_id)
|
||||
`
|
||||
CREATE INDEX IF NOT EXISTS attachment_workspace_card_message_idx ON ${Domain.Attachment} (workspace_id, card_id, message_id)
|
||||
`
|
||||
return ['add_attachment_indexes-v8_3', sql]
|
||||
}
|
||||
|
||||
// CREATE TABLE ${Domain.CardPeerGroup}
|
||||
// (
|
||||
// group_id UUID NOT NULL,
|
||||
// workspace_id UUID NOT NULL,
|
||||
// card_id VARCHAR(255) NOT NULL,
|
||||
// created TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
// PRIMARY KEY (group_id,workspace_id, card_id)
|
||||
// );
|
||||
|
||||
function migrationV9_1 (): [string, string] {
|
||||
const sql = `
|
||||
CREATE TABLE IF NOT EXISTS ${Domain.Peer}
|
||||
(
|
||||
workspace_id UUID NOT NULL,
|
||||
card_id VARCHAR(255) NOT NULL,
|
||||
kind TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
extra JSONB NOT NULL DEFAULT '{}',
|
||||
created TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (workspace_id, card_id, kind, value)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS peer_workspace_card_kind ON ${Domain.Peer} (workspace_id, card_id, kind);
|
||||
CREATE INDEX IF NOT EXISTS peer_kind_value ON ${Domain.Peer} (kind, value);`
|
||||
return ['init_peer_tables-v9_1', sql]
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ import {
|
||||
type LabelID,
|
||||
type CardType,
|
||||
NotificationContent,
|
||||
NotificationType, AttachmentID
|
||||
NotificationType, AttachmentID,
|
||||
PeerKind, PeerExtra
|
||||
} from '@hcengineering/communication-types'
|
||||
import { Domain } from '@hcengineering/communication-sdk-types'
|
||||
|
||||
@@ -130,6 +131,14 @@ export const schemas = {
|
||||
last_view: 'timestamptz',
|
||||
last_update: 'timestamptz',
|
||||
last_notify: 'timestamptz'
|
||||
},
|
||||
[Domain.Peer]: {
|
||||
workspace_id: 'uuid',
|
||||
card_id: 'varchar',
|
||||
kind: 'varchar',
|
||||
value: 'varchar',
|
||||
extra: 'jsonb',
|
||||
created: 'timestamptz'
|
||||
}
|
||||
} as const
|
||||
|
||||
@@ -141,11 +150,11 @@ export interface DomainDbModel {
|
||||
[Domain.Reaction]: ReactionDbModel
|
||||
[Domain.Thread]: ThreadDbModel
|
||||
[Domain.Attachment]: AttachmentDbModel
|
||||
// [Domain.LinkPreview]: LinkPreviewDbModel
|
||||
[Domain.Notification]: NotificationDbModel
|
||||
[Domain.NotificationContext]: ContextDbModel
|
||||
[Domain.Collaborator]: CollaboratorDbModel
|
||||
[Domain.Label]: LabelDbModel
|
||||
[Domain.Peer]: PeerDbModel
|
||||
}
|
||||
|
||||
export type DbModel<D extends keyof DomainDbModel> = DomainDbModel[D]
|
||||
@@ -274,3 +283,12 @@ interface LabelDbModel {
|
||||
account: AccountID
|
||||
created: Date
|
||||
}
|
||||
|
||||
interface PeerDbModel {
|
||||
workspace_id: WorkspaceID
|
||||
card_id: CardID
|
||||
kind: PeerKind
|
||||
value: string
|
||||
extra: PeerExtra
|
||||
created: Date
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
NotificationType,
|
||||
AttachmentData,
|
||||
AttachmentID,
|
||||
AttachmentUpdateData, WithTotal
|
||||
AttachmentUpdateData, WithTotal, WorkspaceID, PeerKind, PeerExtra, FindPeersParams, Peer, FindThreadParams
|
||||
} from '@hcengineering/communication-types'
|
||||
|
||||
export interface DbAdapter {
|
||||
@@ -82,9 +82,25 @@ export interface DbAdapter {
|
||||
removeThreads: (query: ThreadQuery) => Promise<void>
|
||||
updateThread: (cardId: CardID, messageId: MessageID, thread: CardID, update: ThreadUpdates, socialId: SocialID, date: Date) => Promise<void>
|
||||
|
||||
createPeer: (
|
||||
workspaceId: WorkspaceID,
|
||||
cardId: CardID,
|
||||
kind: PeerKind,
|
||||
value: string,
|
||||
extra: PeerExtra,
|
||||
date: Date
|
||||
) => Promise<void>
|
||||
|
||||
removePeer: (workspaceId: WorkspaceID,
|
||||
cardId: CardID,
|
||||
kind: PeerKind,
|
||||
value: string) => Promise<void>
|
||||
|
||||
findPeers: (params: FindPeersParams) => Promise<Peer[]>
|
||||
|
||||
findMessages: (params: FindMessagesParams) => Promise<Message[]>
|
||||
findMessagesGroups: (params: FindMessagesGroupsParams) => Promise<MessagesGroup[]>
|
||||
findThread: (threadId: CardID) => Promise<Thread | undefined>
|
||||
findThreads: (params: FindThreadParams) => Promise<Thread[]>
|
||||
|
||||
addCollaborators: (cardId: CardID, cardType: CardType, collaborators: AccountID[], date: Date) => Promise<AccountID[]>
|
||||
removeCollaborators: (cardId: CardID, accounts: AccountID[], unsafe?: boolean) => Promise<void>
|
||||
@@ -126,6 +142,7 @@ export interface DbAdapter {
|
||||
updateLabels: (cardId: CardID, update: LabelUpdates) => Promise<void>
|
||||
|
||||
getCardTitle: (cardId: CardID) => Promise<string | undefined>
|
||||
getCardSpaceMembers: (cardId: CardID) => Promise<AccountID[]>
|
||||
getAccountsByPersonIds: (ids: string[]) => Promise<AccountID[]>
|
||||
getNameByAccount: (id: AccountID) => Promise<string | undefined>
|
||||
getMessageCreated: (cardId: CardID, messageId: MessageID) => Promise<Date | undefined>
|
||||
|
||||
@@ -26,7 +26,8 @@ export enum Domain {
|
||||
|
||||
Collaborator = 'communication.collaborator',
|
||||
Label = 'communication.label',
|
||||
// LinkPreview = 'communication.link_preview'
|
||||
|
||||
Peer = 'communication.peer'
|
||||
}
|
||||
|
||||
export const Domains = Object.values(Domain)
|
||||
|
||||
@@ -18,14 +18,12 @@ import type { CardID, CardType, SocialID } from '@hcengineering/communication-ty
|
||||
import type { BaseEvent } from './common'
|
||||
|
||||
export enum CardEventType {
|
||||
// Internal
|
||||
UpdateCardType = 'updateCardType',
|
||||
RemoveCard = 'removeCard'
|
||||
}
|
||||
|
||||
export type CardEvent = UpdateCardTypeEvent | RemoveCardEvent
|
||||
|
||||
// Internal
|
||||
export interface UpdateCardTypeEvent extends BaseEvent {
|
||||
type: CardEventType.UpdateCardType
|
||||
cardId: CardID
|
||||
|
||||
@@ -15,13 +15,15 @@ import type { LabelEvent, LabelEventType } from './label'
|
||||
import type { MessageEventResult, MessageEventType, MessageEvent } from './message'
|
||||
import type { NotificationEventResult, NotificationEvent, NotificationEventType } from './notification'
|
||||
import type { CardEvent, CardEventType } from './card'
|
||||
import { PeerEvent, PeerEventType } from './peer'
|
||||
|
||||
export * from './message'
|
||||
export * from './notification'
|
||||
export * from './label'
|
||||
export * from './card'
|
||||
export * from './peer'
|
||||
|
||||
export type EventType = MessageEventType | NotificationEventType | LabelEventType | CardEventType
|
||||
export type Event = MessageEvent | NotificationEvent | LabelEvent | CardEvent
|
||||
export type EventType = MessageEventType | NotificationEventType | LabelEventType | CardEventType | PeerEventType
|
||||
export type Event = MessageEvent | NotificationEvent | LabelEvent | CardEvent | PeerEvent
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export type EventResult = MessageEventResult | NotificationEventResult | {}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// 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 type { BaseEvent } from './common'
|
||||
import { CardID, PeerKind, PeerExtra, WorkspaceID } from '@hcengineering/communication-types'
|
||||
|
||||
// Peer events only for system
|
||||
export enum PeerEventType {
|
||||
CreatePeer = 'createPeer',
|
||||
RemovePeer = 'removePeer'
|
||||
}
|
||||
|
||||
export type PeerEvent = CreatePeerEvent | RemovePeerEvent
|
||||
|
||||
export interface CreatePeerEvent extends BaseEvent {
|
||||
type: PeerEventType.CreatePeer
|
||||
workspaceId: WorkspaceID
|
||||
cardId: CardID
|
||||
kind: PeerKind
|
||||
value: string
|
||||
extra?: PeerExtra
|
||||
date?: Date
|
||||
}
|
||||
|
||||
export interface RemovePeerEvent extends BaseEvent {
|
||||
type: PeerEventType.RemovePeer
|
||||
workspaceId: WorkspaceID
|
||||
cardId: CardID
|
||||
kind: PeerKind
|
||||
value: string
|
||||
date?: Date
|
||||
}
|
||||
@@ -25,7 +25,8 @@ import type {
|
||||
FindLabelsParams,
|
||||
Label,
|
||||
FindCollaboratorsParams,
|
||||
Collaborator
|
||||
Collaborator,
|
||||
FindThreadParams, Thread, FindPeersParams, Peer
|
||||
} from '@hcengineering/communication-types'
|
||||
import type { Account, MeasureContext } from '@hcengineering/core'
|
||||
|
||||
@@ -61,6 +62,9 @@ export interface ServerApi {
|
||||
findLabels: (session: SessionData, params: FindLabelsParams) => Promise<Label[]>
|
||||
findCollaborators: (session: SessionData, params: FindCollaboratorsParams) => Promise<Collaborator[]>
|
||||
|
||||
findThreads: (session: SessionData, params: FindThreadParams) => Promise<Thread[]>
|
||||
findPeers: (session: SessionData, params: FindPeersParams) => Promise<Peer[]>
|
||||
|
||||
event: (session: SessionData, event: Event) => Promise<EventResult>
|
||||
|
||||
unsubscribeQuery: (session: SessionData, id: number) => Promise<void>
|
||||
|
||||
@@ -27,7 +27,8 @@ import type {
|
||||
FindLabelsParams,
|
||||
Label,
|
||||
FindCollaboratorsParams,
|
||||
Collaborator
|
||||
Collaborator, FindPeersParams, Peer, Thread,
|
||||
FindThreadParams
|
||||
} from '@hcengineering/communication-types'
|
||||
import { createDbAdapter } from '@hcengineering/communication-cockroach'
|
||||
import type { EventResult, Event, ServerApi, SessionData } from '@hcengineering/communication-sdk-types'
|
||||
@@ -52,8 +53,9 @@ export class Api implements ServerApi {
|
||||
withLogs: process.env.COMMUNICATION_TIME_LOGGING_ENABLED === 'true'
|
||||
})
|
||||
|
||||
const peers = await db.findPeers({ workspaceId: workspace })
|
||||
const metadata = getMetadata()
|
||||
const middleware = await buildMiddlewares(ctx, workspace, metadata, db, callbacks)
|
||||
const middleware = await buildMiddlewares(ctx, workspace, metadata, db, callbacks, peers)
|
||||
|
||||
return new Api(ctx, middleware)
|
||||
}
|
||||
@@ -90,6 +92,14 @@ export class Api implements ServerApi {
|
||||
return await this.middlewares.findCollaborators(session, params)
|
||||
}
|
||||
|
||||
async findPeers (session: SessionData, params: FindPeersParams): Promise<Peer[]> {
|
||||
return await this.middlewares.findPeers(session, params)
|
||||
}
|
||||
|
||||
async findThreads (session: SessionData, params: FindThreadParams): Promise<Thread[]> {
|
||||
return await this.middlewares.findThreads(session, params)
|
||||
}
|
||||
|
||||
async unsubscribeQuery (session: SessionData, id: number): Promise<void> {
|
||||
await this.middlewares.unsubscribeQuery(session, id)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,11 @@ import type {
|
||||
FindLabelsParams,
|
||||
Label,
|
||||
FindCollaboratorsParams,
|
||||
Collaborator
|
||||
Collaborator,
|
||||
FindPeersParams,
|
||||
Peer,
|
||||
FindThreadParams,
|
||||
Thread
|
||||
} from '@hcengineering/communication-types'
|
||||
|
||||
import type { Enriched, Middleware, MiddlewareContext, QueryId } from '../types'
|
||||
@@ -73,6 +77,14 @@ export class BaseMiddleware implements Middleware {
|
||||
return await this.provideFindCollaborators(session, params)
|
||||
}
|
||||
|
||||
async findPeers (session: SessionData, params: FindPeersParams): Promise<Peer[]> {
|
||||
return await this.provideFindPeers(session, params)
|
||||
}
|
||||
|
||||
async findThreads (session: SessionData, params: FindThreadParams): Promise<Thread[]> {
|
||||
return await this.provideFindThreads(session, params)
|
||||
}
|
||||
|
||||
async event (session: SessionData, event: Enriched<Event>, derived: boolean): Promise<EventResult> {
|
||||
return await this.provideEvent(session, event, derived)
|
||||
}
|
||||
@@ -162,6 +174,20 @@ export class BaseMiddleware implements Middleware {
|
||||
return []
|
||||
}
|
||||
|
||||
protected async provideFindPeers (session: SessionData, params: FindPeersParams): Promise<Peer[]> {
|
||||
if (this.next !== undefined) {
|
||||
return await this.next.findPeers(session, params)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
protected async provideFindThreads (session: SessionData, params: FindThreadParams): Promise<Thread[]> {
|
||||
if (this.next !== undefined) {
|
||||
return await this.next.findThreads(session, params)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
protected provideHandleBroadcast (session: SessionData, events: Enriched<Event>[]): void {
|
||||
if (this.next !== undefined) {
|
||||
this.next.handleBroadcast(session, events)
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
LabelEventType,
|
||||
MessageEventType,
|
||||
NotificationEventType,
|
||||
PeerEventType,
|
||||
type SessionData
|
||||
} from '@hcengineering/communication-sdk-types'
|
||||
import type {
|
||||
@@ -223,6 +224,9 @@ export class BroadcastMiddleware extends BaseMiddleware implements Middleware {
|
||||
case CardEventType.UpdateCardType:
|
||||
case CardEventType.RemoveCard:
|
||||
return true
|
||||
case PeerEventType.RemovePeer:
|
||||
case PeerEventType.CreatePeer:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ export class DateMiddleware extends BaseMiddleware implements Middleware {
|
||||
event.date = new Date()
|
||||
}
|
||||
|
||||
event._eventExtra = {}
|
||||
|
||||
return await this.provideEvent(session, event, derived)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ import {
|
||||
type FindMessagesParams,
|
||||
type FindNotificationContextParams,
|
||||
type FindNotificationsParams,
|
||||
FindPeersParams,
|
||||
FindThreadParams,
|
||||
type Label,
|
||||
type Message,
|
||||
MessageID,
|
||||
@@ -30,7 +32,9 @@ import {
|
||||
type Notification,
|
||||
type NotificationContext,
|
||||
PatchType,
|
||||
Peer,
|
||||
SocialID,
|
||||
Thread,
|
||||
UpdatePatchData
|
||||
} from '@hcengineering/communication-types'
|
||||
import {
|
||||
@@ -62,7 +66,10 @@ import {
|
||||
ThreadPatchEvent,
|
||||
EventResult,
|
||||
AttachmentPatchEvent,
|
||||
BlobPatchEvent
|
||||
BlobPatchEvent,
|
||||
PeerEventType,
|
||||
CreatePeerEvent,
|
||||
RemovePeerEvent
|
||||
} from '@hcengineering/communication-sdk-types'
|
||||
|
||||
import type { Enriched, Middleware, MiddlewareContext } from '../types'
|
||||
@@ -109,11 +116,21 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
|
||||
return await this.db.findCollaborators(params)
|
||||
}
|
||||
|
||||
async event (session: SessionData, event: Enriched<Event>): Promise<EventResult> {
|
||||
async findPeers (_: SessionData, params: FindPeersParams): Promise<Peer[]> {
|
||||
return await this.db.findPeers(params)
|
||||
}
|
||||
|
||||
async findThreads (_: SessionData, params: FindThreadParams): Promise<Thread[]> {
|
||||
return await this.db.findThreads(params)
|
||||
}
|
||||
|
||||
async event (session: SessionData, event: Enriched<Event>, derived: boolean): Promise<EventResult> {
|
||||
const result = await this.processEvent(session, event)
|
||||
|
||||
if (result.skipPropagate === true) {
|
||||
event.skipPropagate = true
|
||||
} else {
|
||||
await this.provideEvent(session, event, derived)
|
||||
}
|
||||
|
||||
return result.result ?? {}
|
||||
@@ -153,6 +170,12 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
|
||||
case CardEventType.RemoveCard:
|
||||
return await this.removeCard(event)
|
||||
|
||||
// Peers
|
||||
case PeerEventType.RemovePeer:
|
||||
return await this.removePeer(event)
|
||||
case PeerEventType.CreatePeer:
|
||||
return await this.createPeer(event)
|
||||
|
||||
// Collaborators
|
||||
case NotificationEventType.AddCollaborators:
|
||||
return await this.addCollaborators(event)
|
||||
@@ -460,6 +483,16 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
|
||||
return {}
|
||||
}
|
||||
|
||||
private async createPeer (event: Enriched<CreatePeerEvent>): Promise<Result> {
|
||||
await this.db.createPeer(event.workspaceId, event.cardId, event.kind, event.value, event.extra ?? {}, event.date)
|
||||
return {}
|
||||
}
|
||||
|
||||
private async removePeer (event: Enriched<RemovePeerEvent>): Promise<Result> {
|
||||
await this.db.removePeer(event.workspaceId, event.cardId, event.kind, event.value)
|
||||
return {}
|
||||
}
|
||||
|
||||
private async updateCardType (event: Enriched<UpdateCardTypeEvent>): Promise<Result> {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// 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 {
|
||||
type Event,
|
||||
EventResult,
|
||||
MessageEventType,
|
||||
PeerEventType,
|
||||
type SessionData
|
||||
} from '@hcengineering/communication-sdk-types'
|
||||
|
||||
import type { Enriched, Middleware, MiddlewareContext } from '../types'
|
||||
import { BaseMiddleware } from './base'
|
||||
|
||||
export class PeerMiddleware extends BaseMiddleware implements Middleware {
|
||||
constructor (
|
||||
readonly context: MiddlewareContext,
|
||||
next?: Middleware
|
||||
) {
|
||||
super(context, next)
|
||||
}
|
||||
|
||||
async event (session: SessionData, event: Enriched<Event>, derived: boolean): Promise<EventResult> {
|
||||
switch (event.type) {
|
||||
case PeerEventType.CreatePeer:
|
||||
this.context.cadsWithPeers.add(event.cardId)
|
||||
break
|
||||
case MessageEventType.CreateMessage:
|
||||
case MessageEventType.UpdatePatch:
|
||||
case MessageEventType.RemovePatch:
|
||||
case MessageEventType.AttachmentPatch:
|
||||
case MessageEventType.ReactionPatch:
|
||||
case MessageEventType.ThreadPatch:
|
||||
case MessageEventType.BlobPatch: {
|
||||
if (this.context.cadsWithPeers.has(event.cardId)) {
|
||||
event._eventExtra.peers =
|
||||
(await this.context.head?.findPeers(session, {
|
||||
workspaceId: this.context.workspace,
|
||||
cardId: event.cardId
|
||||
})) ?? []
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return await this.provideEvent(session, event, derived)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
EventResult,
|
||||
MessageEventType,
|
||||
NotificationEventType,
|
||||
PeerEventType,
|
||||
type SessionData
|
||||
} from '@hcengineering/communication-sdk-types'
|
||||
import { AccountRole, systemAccountUuid } from '@hcengineering/core'
|
||||
@@ -66,6 +67,8 @@ export class PermissionsMiddleware extends BaseMiddleware implements Middleware
|
||||
this.checkAccount(session, event.account)
|
||||
break
|
||||
}
|
||||
case PeerEventType.CreatePeer:
|
||||
case PeerEventType.RemovePeer:
|
||||
case MessageEventType.CreateMessagesGroup:
|
||||
case MessageEventType.RemoveMessagesGroup: {
|
||||
this.onlySystemAccount(session)
|
||||
|
||||
@@ -23,6 +23,7 @@ import { notify } from '../notification/notification'
|
||||
|
||||
export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
||||
private ctx: MeasureContext
|
||||
private processedPeersEvents = new Set<string>()
|
||||
|
||||
constructor (
|
||||
private readonly callbacks: CommunicationCallbacks,
|
||||
@@ -60,6 +61,7 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
||||
registeredCards: this.context.registeredCards,
|
||||
accountBySocialID: this.context.accountBySocialID,
|
||||
removedContexts: this.context.removedContexts,
|
||||
processedPeersEvents: this.processedPeersEvents,
|
||||
derived,
|
||||
execute: async (event: Event) => {
|
||||
// Will be enriched in head
|
||||
@@ -90,6 +92,7 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
||||
(session.asyncData as Enriched<Event>[]).sort((a, b) => a.date.getTime() - b.date.getTime())
|
||||
)
|
||||
session.asyncData = []
|
||||
this.processedPeersEvents = new Set()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ import {
|
||||
MessageEventType,
|
||||
NotificationEventType,
|
||||
type Event,
|
||||
type SessionData
|
||||
type SessionData,
|
||||
PeerEventType
|
||||
} from '@hcengineering/communication-sdk-types'
|
||||
import {
|
||||
type Collaborator,
|
||||
@@ -151,11 +152,18 @@ export class ValidateMiddleware extends BaseMiddleware implements Middleware {
|
||||
case NotificationEventType.UpdateNotificationContext:
|
||||
this.validate(event, UpdateNotificationContextEventSchema)
|
||||
break
|
||||
case PeerEventType.CreatePeer:
|
||||
this.validate(event, CreatePeerEventSchema)
|
||||
break
|
||||
case PeerEventType.RemovePeer:
|
||||
this.validate(event, RemovePeerEventSchema)
|
||||
break
|
||||
}
|
||||
return await this.provideEvent(session, deserializeEvent(event), derived)
|
||||
}
|
||||
}
|
||||
|
||||
const WorkspaceIDSchema = z.string().uuid()
|
||||
const AccountIDSchema = z.string()
|
||||
const BlobIDSchema = z.string().uuid()
|
||||
const AttachmentIDSchema = z.string().uuid()
|
||||
@@ -290,7 +298,8 @@ const FindCollaboratorsParamsSchema = FindParamsSchema.extend({
|
||||
|
||||
const BaseEventSchema = z
|
||||
.object({
|
||||
_id: z.string().optional()
|
||||
_id: z.string().optional(),
|
||||
_eventExtra: z.record(z.any()).optional()
|
||||
})
|
||||
.strict()
|
||||
|
||||
@@ -472,6 +481,25 @@ const RemoveCollaboratorsEventSchema = BaseEventSchema.extend({
|
||||
date: DateSchema
|
||||
}).strict()
|
||||
|
||||
const CreatePeerEventSchema = BaseEventSchema.extend({
|
||||
type: z.literal(PeerEventType.CreatePeer),
|
||||
workspaceId: WorkspaceIDSchema,
|
||||
cardId: CardIDSchema,
|
||||
kind: z.string().nonempty(),
|
||||
value: z.string().nonempty(),
|
||||
extra: z.record(z.any()).optional(),
|
||||
date: DateSchema
|
||||
}).strict()
|
||||
|
||||
const RemovePeerEventSchema = BaseEventSchema.extend({
|
||||
type: z.literal(PeerEventType.RemovePeer),
|
||||
workspaceId: WorkspaceIDSchema,
|
||||
cardId: CardIDSchema,
|
||||
kind: z.string().nonempty(),
|
||||
value: z.string().nonempty(),
|
||||
date: DateSchema
|
||||
}).strict()
|
||||
|
||||
function deserializeEvent (event: Enriched<Event>): Enriched<Event> {
|
||||
switch (event.type) {
|
||||
case MessageEventType.CreateMessagesGroup:
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { MeasureContext } from '@hcengineering/core'
|
||||
import { MeasureContext } from '@hcengineering/core'
|
||||
import type { DbAdapter, EventResult, Event, SessionData } from '@hcengineering/communication-sdk-types'
|
||||
import type {
|
||||
Collaborator,
|
||||
@@ -23,11 +23,14 @@ import type {
|
||||
FindMessagesParams,
|
||||
FindNotificationContextParams,
|
||||
FindNotificationsParams,
|
||||
FindPeersParams,
|
||||
FindThreadParams,
|
||||
Label,
|
||||
Message,
|
||||
MessagesGroup,
|
||||
Notification,
|
||||
NotificationContext,
|
||||
NotificationContext, Peer,
|
||||
Thread,
|
||||
WorkspaceID
|
||||
} from '@hcengineering/communication-types'
|
||||
|
||||
@@ -48,13 +51,14 @@ import { ValidateMiddleware } from './middleware/validate'
|
||||
import { DateMiddleware } from './middleware/date'
|
||||
import { IdentityMiddleware } from './middleware/indentity'
|
||||
import { IdMiddleware } from './middleware/id'
|
||||
import { PeerMiddleware } from './middleware/peer'
|
||||
|
||||
export async function buildMiddlewares (
|
||||
ctx: MeasureContext,
|
||||
workspace: WorkspaceID,
|
||||
metadata: Metadata,
|
||||
db: DbAdapter,
|
||||
callbacks: CommunicationCallbacks
|
||||
callbacks: CommunicationCallbacks, peers: Peer[]
|
||||
): Promise<Middlewares> {
|
||||
const createFns: MiddlewareCreateFn[] = [
|
||||
// Enrich events
|
||||
@@ -69,7 +73,8 @@ export async function buildMiddlewares (
|
||||
// Process events
|
||||
async (context, next) => new TriggersMiddleware(callbacks, db, context, next),
|
||||
async (context, next) => new BroadcastMiddleware(callbacks, context, next),
|
||||
async (context, next) => new DatabaseMiddleware(db, context, next)
|
||||
async (context, next) => new DatabaseMiddleware(db, context, next),
|
||||
async (context, next) => new PeerMiddleware(context, next)
|
||||
]
|
||||
|
||||
const context: MiddlewareContext = {
|
||||
@@ -78,7 +83,8 @@ export async function buildMiddlewares (
|
||||
workspace,
|
||||
registeredCards: new Set(),
|
||||
accountBySocialID: new Map(),
|
||||
removedContexts: new Map()
|
||||
removedContexts: new Map(),
|
||||
cadsWithPeers: new Set(peers.map(it => it.cardId))
|
||||
}
|
||||
|
||||
return await Middlewares.create(ctx, context, createFns)
|
||||
@@ -169,6 +175,16 @@ export class Middlewares {
|
||||
return await this.head.findCollaborators(session, params)
|
||||
}
|
||||
|
||||
async findPeers (session: SessionData, params: FindPeersParams): Promise<Peer[]> {
|
||||
if (this.head === undefined) return []
|
||||
return await this.head.findPeers(session, params)
|
||||
}
|
||||
|
||||
async findThreads (session: SessionData, params: FindThreadParams): Promise<Thread[]> {
|
||||
if (this.head === undefined) return []
|
||||
return await this.head.findThreads(session, params)
|
||||
}
|
||||
|
||||
async unsubscribeQuery (session: SessionData, id: number): Promise<void> {
|
||||
if (this.head === undefined) return
|
||||
this.head?.unsubscribeQuery(session, id)
|
||||
|
||||
@@ -220,6 +220,7 @@ async function notifyMessage (
|
||||
date: Date
|
||||
): Promise<Event[]> {
|
||||
const cursor = ctx.db.getCollaboratorsCursor(cardId, date, BATCH_SIZE)
|
||||
const spaceMembers = await ctx.db.getCardSpaceMembers(cardId)
|
||||
const creatorAccount = await findAccount(ctx, socialId)
|
||||
const result: Event[] = []
|
||||
|
||||
@@ -235,6 +236,7 @@ async function notifyMessage (
|
||||
})
|
||||
|
||||
for (const collaborator of collaborators) {
|
||||
if (!spaceMembers.includes(collaborator)) continue
|
||||
try {
|
||||
const context = contexts.find((it) => it.account === collaborator)
|
||||
const res = await processCollaborator(
|
||||
|
||||
@@ -52,7 +52,7 @@ async function onCardTypeUpdates (ctx: TriggerCtx, event: Enriched<UpdateCardTyp
|
||||
await ctx.db.updateCollaborators({ card: event.cardId }, { cardType: event.cardType })
|
||||
await ctx.db.updateLabels(event.cardId, { cardType: event.cardType })
|
||||
|
||||
const thread = await ctx.db.findThread(event.cardId)
|
||||
const thread = (await ctx.db.findThreads({ threadId: event.cardId, limit: 1 }))[0]
|
||||
if (thread === undefined) return []
|
||||
|
||||
return [
|
||||
|
||||
@@ -23,9 +23,9 @@ import {
|
||||
RemovePatchEvent,
|
||||
ThreadPatchEvent
|
||||
} from '@hcengineering/communication-sdk-types'
|
||||
import { type CardID, MessageType } from '@hcengineering/communication-types'
|
||||
import { type CardID, CardPeer, MessageType, Peer } from '@hcengineering/communication-types'
|
||||
import { generateToken } from '@hcengineering/server-token'
|
||||
import { type AccountUuid, concatLink, systemAccountUuid } from '@hcengineering/core'
|
||||
import { type AccountUuid, concatLink, generateId, systemAccountUuid } from '@hcengineering/core'
|
||||
import { extractReferences } from '@hcengineering/text-core'
|
||||
import { markdownToMarkup } from '@hcengineering/text-markdown'
|
||||
|
||||
@@ -41,7 +41,7 @@ async function onMessagesGroupCreated (ctx: TriggerCtx, event: CreateMessagesGro
|
||||
|
||||
async function onMessageRemoved (ctx: TriggerCtx, event: Enriched<RemovePatchEvent>): Promise<Event[]> {
|
||||
const { cardId } = event
|
||||
const thread = await ctx.db.findThread(cardId)
|
||||
const thread = (await ctx.db.findThreads({ threadId: cardId, limit: 1 }))[0]
|
||||
if (thread === undefined) return []
|
||||
|
||||
return [
|
||||
@@ -126,7 +126,7 @@ async function addThreadReply (ctx: TriggerCtx, event: Enriched<CreateMessageEve
|
||||
return []
|
||||
}
|
||||
const { cardId, socialId, date } = event
|
||||
const thread = await ctx.db.findThread(cardId)
|
||||
const thread = (await ctx.db.findThreads({ threadId: cardId, limit: 1 }))[0]
|
||||
|
||||
if (thread === undefined) return []
|
||||
|
||||
@@ -201,6 +201,43 @@ async function onThreadAttached (ctx: TriggerCtx, event: Enriched<ThreadPatchEve
|
||||
return result
|
||||
}
|
||||
|
||||
async function checkPeers (ctx: TriggerCtx, event: Enriched<CreateMessageEvent | PatchEvent>): Promise<Event[]> {
|
||||
if (ctx.processedPeersEvents.has(event._id)) return []
|
||||
if (event.type === MessageEventType.CreateMessage) {
|
||||
if (event.messageType === MessageType.Activity) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === MessageEventType.ThreadPatch) {
|
||||
return []
|
||||
}
|
||||
|
||||
const cardPeers = new Set(
|
||||
(((event._eventExtra.peers ?? []) as Peer[]).filter((it) => it.kind === 'card') as CardPeer[])
|
||||
.flatMap((it) => it.members)
|
||||
.filter((it) => it.workspaceId === ctx.workspace && it.cardId !== event.cardId)
|
||||
.map((it) => it.cardId)
|
||||
)
|
||||
|
||||
if (cardPeers.size === 0) return []
|
||||
const res: Event[] = []
|
||||
|
||||
for (const peer of cardPeers) {
|
||||
const ev = {
|
||||
...event,
|
||||
_id: generateId(),
|
||||
cardId: peer
|
||||
}
|
||||
|
||||
ctx.processedPeersEvents.add(ev._id)
|
||||
|
||||
res.push(ev)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const triggers: Triggers = [
|
||||
['add_collaborators_on_message_created', MessageEventType.CreateMessage, addCollaborators as TriggerFn],
|
||||
['add_thread_reply_on_message_created', MessageEventType.CreateMessage, addThreadReply as TriggerFn],
|
||||
@@ -214,7 +251,15 @@ const triggers: Triggers = [
|
||||
|
||||
['on_messages_group_created', MessageEventType.CreateMessagesGroup, onMessagesGroupCreated as TriggerFn],
|
||||
['remove_reply_on_messages_removed', MessageEventType.RemovePatch, onMessageRemoved as TriggerFn],
|
||||
['on_thread_created', MessageEventType.ThreadPatch, onThreadAttached as TriggerFn]
|
||||
['on_thread_created', MessageEventType.ThreadPatch, onThreadAttached as TriggerFn],
|
||||
|
||||
['check_peers_on_message_created', MessageEventType.CreateMessage, checkPeers as TriggerFn],
|
||||
['check_peers_on_update_patch', MessageEventType.UpdatePatch, checkPeers as TriggerFn],
|
||||
['check_peers_on_remove_patch', MessageEventType.RemovePatch, checkPeers as TriggerFn],
|
||||
['check_peers_on_reaction_patch', MessageEventType.ReactionPatch, checkPeers as TriggerFn],
|
||||
['check_peers_on_blob_patch', MessageEventType.BlobPatch, checkPeers as TriggerFn],
|
||||
['check_peers_on_attachment_patch', MessageEventType.AttachmentPatch, checkPeers as TriggerFn],
|
||||
['check_peers_on_thread_patch', MessageEventType.ThreadPatch, checkPeers as TriggerFn]
|
||||
]
|
||||
|
||||
export default triggers
|
||||
|
||||
@@ -23,19 +23,23 @@ import type {
|
||||
import type {
|
||||
AccountID,
|
||||
CardID,
|
||||
Collaborator, ContextID,
|
||||
Collaborator,
|
||||
ContextID,
|
||||
FindCollaboratorsParams,
|
||||
FindLabelsParams,
|
||||
FindMessagesGroupsParams,
|
||||
FindMessagesParams,
|
||||
FindNotificationContextParams,
|
||||
FindNotificationsParams,
|
||||
FindPeersParams,
|
||||
FindThreadParams,
|
||||
Label,
|
||||
Message,
|
||||
MessagesGroup,
|
||||
Notification,
|
||||
NotificationContext,
|
||||
SocialID,
|
||||
Peer,
|
||||
SocialID, Thread,
|
||||
WorkspaceID
|
||||
} from '@hcengineering/communication-types'
|
||||
|
||||
@@ -69,6 +73,8 @@ export interface Middleware {
|
||||
|
||||
findLabels: (session: SessionData, params: FindLabelsParams, queryId?: QueryId) => Promise<Label[]>
|
||||
findCollaborators: (session: SessionData, params: FindCollaboratorsParams) => Promise<Collaborator[]>
|
||||
findPeers: (session: SessionData, params: FindPeersParams) => Promise<Peer[]>
|
||||
findThreads: (session: SessionData, params: FindThreadParams) => Promise<Thread[]>
|
||||
|
||||
event: (session: SessionData, event: Enriched<Event>, derived: boolean) => Promise<EventResult>
|
||||
|
||||
@@ -88,6 +94,8 @@ export interface MiddlewareContext {
|
||||
accountBySocialID: Map<SocialID, AccountID>
|
||||
removedContexts: Map<ContextID, NotificationContext>
|
||||
|
||||
cadsWithPeers: Set<CardID>
|
||||
|
||||
derived?: Middleware
|
||||
head?: Middleware
|
||||
}
|
||||
@@ -110,6 +118,7 @@ export interface TriggerCtx {
|
||||
accountBySocialID: Map<SocialID, AccountID>
|
||||
removedContexts: Map<ContextID, NotificationContext>
|
||||
derived: boolean
|
||||
processedPeersEvents: Set<string>
|
||||
execute: (event: Event) => Promise<EventResult>
|
||||
}
|
||||
|
||||
@@ -117,6 +126,8 @@ export type TriggerFn = (ctx: TriggerCtx, event: Enriched<Event>) => Promise<Eve
|
||||
export type Triggers = [string, EventType, TriggerFn][]
|
||||
|
||||
export type Enriched<T> = T & {
|
||||
_id: string
|
||||
skipPropagate?: boolean
|
||||
date: Date
|
||||
_eventExtra: Record<string, any>
|
||||
}
|
||||
|
||||
@@ -20,3 +20,4 @@ export * from './notification'
|
||||
export * from './query'
|
||||
export * from './label'
|
||||
export * from './patch'
|
||||
export * from './peer'
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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 { CardID, WorkspaceID } from './core'
|
||||
|
||||
export type PeerKind = 'card' | string
|
||||
export type PeerExtra = Record<string, any>
|
||||
|
||||
interface BasePeer {
|
||||
workspaceId: WorkspaceID
|
||||
cardId: CardID
|
||||
kind: PeerKind
|
||||
value: string
|
||||
extra: PeerExtra
|
||||
created: Date
|
||||
}
|
||||
|
||||
export interface CardPeer extends BasePeer {
|
||||
kind: 'card'
|
||||
members: CardPeerMember[]
|
||||
}
|
||||
|
||||
export interface ExternalPeer extends BasePeer {
|
||||
kind: string
|
||||
}
|
||||
|
||||
export type Peer = CardPeer | ExternalPeer
|
||||
|
||||
export interface CardPeerMember {
|
||||
workspaceId: WorkspaceID
|
||||
cardId: CardID
|
||||
extra: PeerExtra
|
||||
}
|
||||
@@ -17,8 +17,9 @@ import { SortingOrder } from '@hcengineering/core'
|
||||
|
||||
import type { MessageID } from './message'
|
||||
import type { ContextID, NotificationID, NotificationType } from './notification'
|
||||
import type { AccountID, BlobID, CardID, CardType } from './core'
|
||||
import type { AccountID, BlobID, CardID, CardType, WorkspaceID } from './core'
|
||||
import type { LabelID } from './label'
|
||||
import { PeerKind } from './peer'
|
||||
|
||||
export { SortingOrder }
|
||||
|
||||
@@ -101,4 +102,17 @@ export interface FindLabelsParams extends FindParams {
|
||||
account?: AccountID
|
||||
}
|
||||
|
||||
export interface FindThreadParams extends FindParams {
|
||||
cardId?: CardID
|
||||
messageId?: MessageID
|
||||
threadId?: CardID
|
||||
}
|
||||
|
||||
export interface FindPeersParams extends FindParams {
|
||||
workspaceId?: WorkspaceID
|
||||
cardId?: CardID
|
||||
kind?: PeerKind
|
||||
value?: string
|
||||
}
|
||||
|
||||
export type WithTotal<T> = T[] & { total: number }
|
||||
|
||||
Reference in New Issue
Block a user