diff --git a/packages/cockroach/src/db/mapping.ts b/packages/cockroach/src/db/mapping.ts index 9ebf1e01d9..08d725db8c 100644 --- a/packages/cockroach/src/db/mapping.ts +++ b/packages/cockroach/src/db/mapping.ts @@ -165,13 +165,21 @@ export function toLinkPreview (raw: LinkPreviewDb): LinkPreview { } export function toMessagesGroup (raw: MessagesGroupDb): MessagesGroup { + const patches = + raw.patches == null + ? [] + : raw.patches + .filter((it: any) => it.message_id != null) + .map(toPatch) + .sort((a, b) => a.created.getTime() - b.created.getTime()) + return { cardId: raw.card_id, blobId: raw.blob_id, fromDate: raw.from_date, toDate: raw.to_date, count: Number(raw.count), - patches: raw.patches == null ? [] : raw.patches.filter((it: any) => it.message_id != null).map(toPatch) + patches } } diff --git a/packages/cockroach/src/db/message.ts b/packages/cockroach/src/db/message.ts index eaffbee23d..aab222c324 100644 --- a/packages/cockroach/src/db/message.ts +++ b/packages/cockroach/src/db/message.ts @@ -1027,33 +1027,53 @@ export class MessagesDb extends BaseDb { // Find messages groups async findMessagesGroups (params: FindMessagesGroupsParams): Promise { - const select = ` - SELECT mg.card_id, - mg.blob_id, - mg.from_date, - mg.to_date, - mg.count, - patches - FROM ${TableName.MessagesGroup} mg - CROSS JOIN LATERAL ( - SELECT jsonb_agg(jsonb_build_object( - 'message_id', p.message_id::text, - 'type', p.type, - 'data', p.data, - 'creator', p.creator, - 'created', p.created - ) ORDER BY p.created) AS patches - FROM ${TableName.Patch} p - WHERE p.workspace_id = mg.workspace_id - AND p.card_id = mg.card_id - AND p.message_created BETWEEN mg.from_date AND mg.to_date - ) sub` + const useMessageIdCte = params.messageId != null + const values: any[] = [this.workspace] + if (useMessageIdCte) values.push(params.messageId) + + const cte = useMessageIdCte + ? ` + WITH msg_created AS ( + SELECT card_id, created + FROM ${TableName.MessageCreated} + WHERE workspace_id = $1::uuid + AND message_id = $2::varchar + ) + ` + : '' + + const select = ` + ${cte} + SELECT mg.card_id, + mg.blob_id, + mg.from_date, + mg.to_date, + mg.count, + patches + FROM ${TableName.MessagesGroup} mg + ${useMessageIdCte ? 'JOIN msg_created mc ON mg.card_id = mc.card_id AND mc.created BETWEEN mg.from_date AND mg.to_date' : ''} + CROSS JOIN LATERAL ( + SELECT jsonb_agg(jsonb_build_object( + 'message_id', p.message_id::varchar, + 'type', p.type, + 'data', p.data, + 'creator', p.creator, + 'created', p.created + )) AS patches + FROM ${TableName.Patch} p + WHERE p.workspace_id = mg.workspace_id + AND p.card_id = mg.card_id + AND p.message_created BETWEEN mg.from_date AND mg.to_date + ) sub + ` + + const { where, values: additionalValues } = this.buildMessagesGroupWhere(params, values.length + 1) + values.push(...additionalValues) - const { where, values } = this.buildMessagesGroupWhere(params) const orderBy = params.orderBy === 'toDate' - ? `ORDER BY mg.to_date ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}` - : `ORDER BY mg.from_date ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}` + ? `ORDER BY mg.to_date ${params.order === SortingOrder.Descending ? 'DESC' : 'ASC'}` + : `ORDER BY mg.from_date ${params.order === SortingOrder.Descending ? 'DESC' : 'ASC'}` const limit = params.limit != null ? ` LIMIT ${params.limit}` : '' const sql = [select, where, orderBy, limit].join(' ') @@ -1062,14 +1082,17 @@ export class MessagesDb extends BaseDb { return result.map((it: any) => toMessagesGroup(it)) } - buildMessagesGroupWhere (params: FindMessagesGroupsParams): { - where: string - values: any[] - } { + buildMessagesGroupWhere ( + params: FindMessagesGroupsParams, + startIndex = 1 + ): { + where: string + values: any[] + } { const where: string[] = ['mg.workspace_id = $1::uuid'] - const values: any[] = [this.workspace] + const values: any[] = [] - let index = 2 + let index = startIndex if (params.card != null) { where.push(`mg.card_id = $${index++}::varchar`) @@ -1099,7 +1122,10 @@ export class MessagesDb extends BaseDb { where.push('sub.patches IS NOT NULL') } - return { where: `WHERE ${where.join(' AND ')}`, values } + return { + where: where.length > 0 ? `WHERE ${where.join(' AND ')}` : '', + values + } } public async isMessageInDb (cardId: CardID, messageId: MessageID): Promise { diff --git a/packages/query/src/notification-contexts/query.ts b/packages/query/src/notification-contexts/query.ts index 90f3bb05b8..000c44b60c 100644 --- a/packages/query/src/notification-contexts/query.ts +++ b/packages/query/src/notification-contexts/query.ts @@ -318,7 +318,7 @@ export class NotificationContextsQuery implements PagedQuery - this.params.order === SortingOrder.Descending - ? (b.lastNotify?.getTime() ?? 0) - (a.lastNotify?.getTime() ?? 0) - : (a.lastNotify?.getTime() ?? 0) - (b.lastNotify?.getTime() ?? 0) - ) + this.sort(this.result) } + void this.notify() } @@ -601,20 +599,29 @@ export class NotificationContextsQuery implements PagedQuery it.read === read) } - private async addContext (context: NotificationContext): Promise { - if (this.result instanceof Promise) this.result = await this.result - if (this.result.get(context.id) !== undefined) return - if (this.result.isTail()) { + private addContext (context: NotificationContext, result: QueryResult): void { + if (result.get(context.id) !== undefined) return + if (result.isTail()) { if (this.params.order === SortingOrder.Ascending) { - this.result.push(context) + result.push(context) } else { - this.result.unshift(context) + result.unshift(context) } } - if (this.params.limit != null && this.result.length > this.params.limit) { - this.result.pop() + if (this.params.limit != null && result.length > this.params.limit) { + result.pop() } + + this.sort(result) + } + + private sort (result: QueryResult): void { + result.sort((a, b) => + this.params.order === SortingOrder.Descending + ? (b.lastNotify?.getTime() ?? 0) - (a.lastNotify?.getTime() ?? 0) + : (a.lastNotify?.getTime() ?? 0) - (b.lastNotify?.getTime() ?? 0) + ) } private match (context: NotificationContext): boolean { diff --git a/packages/server/src/middleware/validate.ts b/packages/server/src/middleware/validate.ts index dfa197b5c3..54bbbe1e40 100644 --- a/packages/server/src/middleware/validate.ts +++ b/packages/server/src/middleware/validate.ts @@ -216,6 +216,7 @@ const FindMessagesParamsSchema = FindParamsSchema.extend({ }).strict() const FindMessagesGroupsParamsSchema = FindParamsSchema.extend({ + messageId: MessageIDSchema.optional(), card: CardIDSchema.optional(), blobId: BlobIDSchema.optional(), patches: z.boolean().optional(), diff --git a/packages/server/src/utils.ts b/packages/server/src/utils.ts index d019242a64..e340dc6992 100644 --- a/packages/server/src/utils.ts +++ b/packages/server/src/utils.ts @@ -13,7 +13,7 @@ // limitations under the License. // -import type { AccountID, MessageID, SocialID } from '@hcengineering/communication-types' +import type { AccountID, SocialID } from '@hcengineering/communication-types' import { generateToken } from '@hcengineering/server-token' import { systemAccountUuid } from '@hcengineering/core' import { getClient as getAccountClient } from '@hcengineering/account-client' @@ -47,11 +47,3 @@ export async function findAccount (ctx: TriggerCtx, socialString: SocialID): Pro ctx.ctx.warn('Cannot find account', { socialString, err }) } } - -export function isExternalMessageId (messageId: MessageID): boolean { - return messageId.startsWith('e') -} - -export function parseMessageIdDate (messageId: MessageID): Date | undefined { - return isExternalMessageId(messageId) ? undefined : new Date(Number(messageId)) -} diff --git a/packages/types/src/query.ts b/packages/types/src/query.ts index cd80a3320b..5f562fb6ce 100644 --- a/packages/types/src/query.ts +++ b/packages/types/src/query.ts @@ -52,6 +52,7 @@ export interface FindMessagesParams extends FindParams { } export interface FindMessagesGroupsParams extends FindParams { + messageId?: MessageID card?: CardID blobId?: BlobID patches?: boolean