Use rowid for message id and allow provide date (#44)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina
2025-04-16 09:59:50 +04:00
committed by GitHub
parent de99172f65
commit 375bdee457
46 changed files with 622 additions and 303 deletions
+1 -1
View File
@@ -1 +1 @@
0.1.168
0.1.170
+3 -1
View File
@@ -39,7 +39,7 @@
"@hcengineering/communication-shared": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
"postgres": "^3.4.4",
"uuid": "^11.0.3",
"uuid": "^11.1.0",
},
"devDependencies": {
"@types/bun": "^1.1.14",
@@ -57,6 +57,7 @@
"@hcengineering/communication-types": "workspace:*",
"@hcengineering/communication-yaml": "workspace:*",
"fast-equals": "^5.0.1",
"uuid": "^11.1.0",
},
"devDependencies": {
"@types/bun": "^1.1.14",
@@ -125,6 +126,7 @@
"name": "@hcengineering/communication-shared",
"version": "0.1.0",
"dependencies": {
"@hcengineering/communication-sdk-types": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
},
"devDependencies": {
+2 -1
View File
@@ -8,7 +8,8 @@
"lint": "turbo run lint",
"format": "turbo run format",
"reformat": "turbo run format && turbo run lint -- --fix",
"format:check": "prettier --check \"**/*.ts\""
"format:check": "prettier --check \"**/*.ts\"",
"clean": "turbo run clean"
},
"devDependencies": {
"@eslint/js": "^9.24.0",
+3 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@hcengineering/communication-client-query",
"version": "0.1.0",
"main": "src/index.ts",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "./types/index.d.ts",
"files": [
@@ -14,7 +14,8 @@
"build:types": "tsc --project ./tsconfig.json --emitDeclarationOnly --declarationDir ./types",
"build:browser": "bun build src/index.ts --outdir dist --target browser",
"lint": "eslint src/*.ts",
"format": "prettier --write src/**/*.ts "
"format": "prettier --write src/**/*.ts ",
"clean": "rm -rf dist && rm -rf types && rm -rf .turbo"
},
"devDependencies": {
"@types/bun": "^1.1.14"
+3 -2
View File
@@ -13,8 +13,9 @@
// limitations under the License.
//
import { LiveQueries, type QueryClient } from '@hcengineering/communication-query'
import { LiveQueries } from '@hcengineering/communication-query'
import type { WorkspaceID } from '@hcengineering/communication-types'
import type { FindClient } from '@hcengineering/communication-sdk-types'
import { LabelsQuery, MessagesQuery, NotificationContextsQuery, NotificationsQuery } from './query'
@@ -38,7 +39,7 @@ export function createLabelsQuery(): LabelsQuery {
}
export function initLiveQueries(
client: QueryClient,
client: FindClient,
workspace: WorkspaceID,
filesUrl: string,
destroyFn?: (fn: () => void) => void
+4 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@hcengineering/communication-cockroach",
"version": "0.1.0",
"main": "src/index.ts",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "./types/index.d.ts",
"files": [
@@ -14,7 +14,8 @@
"build:types": "tsc --project ./tsconfig.json --emitDeclarationOnly --declarationDir ./types",
"build:node": "bun build src/index.ts --target node --format cjs --outfile dist/index.js",
"lint": "eslint src/*.ts",
"format": "prettier --write src/**/*.ts"
"format": "prettier --write src/**/*.ts",
"clean": "rm -rf dist && rm -rf types && rm -rf .turbo"
},
"devDependencies": {
"@types/bun": "^1.1.14"
@@ -24,7 +25,7 @@
"@hcengineering/communication-sdk-types": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
"postgres": "^3.4.4",
"uuid": "^11.0.3"
"uuid": "^11.1.0"
},
"peerDependencies": {
"typescript": "^5.6.3"
+22 -7
View File
@@ -89,12 +89,13 @@ export class CockroachAdapter implements DbAdapter {
async createPatch(
card: CardID,
message: MessageID,
messageCreated: Date,
type: PatchType,
content: RichText,
creator: SocialID,
created: Date
): Promise<void> {
await this.message.createPatch(card, message, type, content, creator, created)
await this.message.createPatch(card, message, messageCreated, type, content, creator, created)
}
async createMessagesGroup(card: CardID, blobId: BlobID, fromDate: Date, toDate: Date, count: number): Promise<void> {
@@ -108,20 +109,28 @@ export class CockroachAdapter implements DbAdapter {
async createReaction(
card: CardID,
message: MessageID,
messageCreated: Date,
reaction: string,
creator: SocialID,
created: Date
): Promise<void> {
await this.message.createReaction(card, message, reaction, creator, created)
await this.message.createReaction(card, message, messageCreated, reaction, creator, created)
}
async removeReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID): Promise<void> {
await this.message.removeReaction(card, message, reaction, creator, new Date())
async removeReaction(
card: CardID,
message: MessageID,
messageCreated: Date,
reaction: string,
creator: SocialID
): Promise<void> {
await this.message.removeReaction(card, message, messageCreated, reaction, creator, new Date())
}
async createFile(
card: CardID,
message: MessageID,
messageCreated: Date,
blobId: BlobID,
fileType: string,
filename: string,
@@ -129,15 +138,21 @@ export class CockroachAdapter implements DbAdapter {
creator: SocialID,
created: Date
): Promise<void> {
await this.message.createFile(card, message, blobId, fileType, filename, size, creator, created)
await this.message.createFile(card, message, messageCreated, blobId, fileType, filename, size, creator, created)
}
async removeFile(card: CardID, message: MessageID, blobId: BlobID): Promise<void> {
await this.message.removeFile(card, message, blobId)
}
async createThread(card: CardID, message: MessageID, thread: CardID, created: Date): Promise<void> {
await this.message.createThread(card, message, thread, created)
async createThread(
card: CardID,
message: MessageID,
messageCreated: Date,
thread: CardID,
created: Date
): Promise<void> {
await this.message.createThread(card, message, messageCreated, thread, created)
}
async updateThread(thread: CardID, op: 'increment' | 'decrement', lastReply?: Date): Promise<void> {
+12 -8
View File
@@ -67,8 +67,8 @@ interface RawNotification extends NotificationDb {
message_external_id?: string
message_created?: Date
message_group_blob_id?: BlobID
message_group_from_sec?: Date
message_group_to_sec?: Date
message_group_from_date?: Date
message_group_to_date?: Date
message_group_count?: number
message_patches?: {
patch_type: PatchType
@@ -100,6 +100,7 @@ export function toMessage(raw: RawMessage): Message {
? {
card: raw.card_id,
message: String(raw.id) as MessageID,
messageCreated: new Date(raw.created),
thread: raw.thread_id,
repliesCount: raw.replies_count ?? 0,
lastReply: raw.last_reply ?? new Date()
@@ -123,6 +124,7 @@ export function toFile(raw: FileDb): File {
return {
card: raw.card_id,
message: String(raw.message_id) as MessageID,
messageCreated: new Date(raw.message_created),
blobId: raw.blob_id,
type: raw.type,
filename: raw.filename,
@@ -136,9 +138,9 @@ export function toMessagesGroup(raw: MessagesGroupDb): MessagesGroup {
return {
card: raw.card_id,
blobId: raw.blob_id,
fromSec: raw.from_sec,
toSec: raw.to_sec,
count: raw.count,
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)
}
}
@@ -146,6 +148,7 @@ export function toMessagesGroup(raw: MessagesGroupDb): MessagesGroup {
export function toPatch(raw: PatchDb): Patch {
return {
type: raw.type,
messageCreated: new Date(raw.message_created),
message: String(raw.message_id) as MessageID,
content: raw.content,
creator: raw.creator,
@@ -157,6 +160,7 @@ export function toThread(raw: ThreadDb): Thread {
return {
card: raw.card_id,
message: String(raw.message_id) as MessageID,
messageCreated: new Date(raw.message_created),
thread: raw.thread_id,
repliesCount: raw.replies_count,
lastReply: raw.last_reply
@@ -223,12 +227,12 @@ function toNotificationRaw(
let messageGroup: MessagesGroup | undefined
if (raw.message_group_blob_id != null && raw.message_group_from_sec != null && raw.message_group_to_sec != null) {
if (raw.message_group_blob_id != null && raw.message_group_from_date != null && raw.message_group_to_date != null) {
messageGroup = {
card,
blobId: raw.message_group_blob_id,
fromSec: new Date(raw.message_group_from_sec),
toSec: new Date(raw.message_group_to_sec),
fromDate: new Date(raw.message_group_from_date),
toDate: new Date(raw.message_group_to_date),
count: raw.message_group_count ?? 0
}
}
+52 -50
View File
@@ -29,7 +29,6 @@ import {
SortingOrder,
type Thread
} from '@hcengineering/communication-types'
import { generateMessageId, parseMessageId } from '@hcengineering/communication-shared'
import { BaseDb } from './base'
import {
@@ -55,9 +54,7 @@ export class MessagesDb extends BaseDb {
data?: MessageData,
externalId?: string
): Promise<MessageID> {
const id = generateMessageId()
const db: MessageDb = {
id,
const db: Omit<MessageDb, 'id'> = {
type,
workspace_id: this.workspace,
card_id: card,
@@ -68,26 +65,17 @@ export class MessagesDb extends BaseDb {
external_id: externalId
}
const sql = `INSERT INTO ${TableName.Message} (workspace_id, card_id, id, content, creator, created, type, data, external_id)
VALUES ($1::uuid, $2::varchar, $3::bigint, $4::text, $5::varchar, $6::timestamptz, $7::varchar, $8::jsonb, $9::varchar)`
const sql = `INSERT INTO ${TableName.Message} (workspace_id, card_id, content, creator, created, type, data, external_id)
VALUES ($1::uuid, $2::varchar, $3::text, $4::varchar, $5::timestamptz, $6::varchar, $7::jsonb, $8::varchar)
RETURNING id::text`
await this.execute(
const result = await this.execute(
sql,
[
db.workspace_id,
db.card_id,
db.id,
db.content,
db.creator,
db.created,
db.type,
db.data ?? {},
externalId ?? null
],
[db.workspace_id, db.card_id, db.content, db.creator, db.created, db.type, db.data ?? {}, externalId ?? null],
'insert message'
)
return id
return result.map((it: any) => it.id)[0]
}
async removeMessages(card: CardID, messages: MessageID[], socialIds?: SocialID[]): Promise<MessageID[]> {
@@ -116,7 +104,7 @@ export class MessagesDb extends BaseDb {
values.push(messages)
}
const sql = `DELETE FROM ${TableName.Message} WHERE ${where.join(' AND ')} RETURNING id`
const sql = `DELETE FROM ${TableName.Message} WHERE ${where.join(' AND ')} RETURNING id::text`
const result = await this.execute(sql, values, 'remove messages')
@@ -126,6 +114,7 @@ export class MessagesDb extends BaseDb {
async createPatch(
card: CardID,
message: MessageID,
messageCreated: Date,
type: PatchType,
content: string,
creator: SocialID,
@@ -139,15 +128,15 @@ export class MessagesDb extends BaseDb {
content,
creator,
created,
message_created_sec: parseMessageId(message)
message_created: messageCreated
}
const sql = `INSERT INTO ${TableName.Patch} (workspace_id, card_id, message_id, type, content, creator, created, message_created_sec)
const sql = `INSERT INTO ${TableName.Patch} (workspace_id, card_id, message_id, type, content, creator, created, message_created)
VALUES ($1::uuid, $2::varchar, $3::bigint, $4::varchar, $5::text, $6::varchar, $7::timestamptz, $8::timestamptz)`
await this.execute(
sql,
[db.workspace_id, db.card_id, db.message_id, db.type, db.content, db.creator, db.created, db.message_created_sec],
[db.workspace_id, db.card_id, db.message_id, db.type, db.content, db.creator, db.created, db.message_created],
'insert patch'
)
}
@@ -156,6 +145,7 @@ export class MessagesDb extends BaseDb {
async createFile(
card: CardID,
message: MessageID,
messageCreated: Date,
blobId: BlobID,
fileType: string,
filename: string,
@@ -173,9 +163,9 @@ export class MessagesDb extends BaseDb {
size,
creator,
created,
message_created_sec: parseMessageId(message)
message_created: messageCreated
}
const sql = `INSERT INTO ${TableName.File} (workspace_id, card_id, message_id, blob_id, type, filename, creator, created, message_created_sec, size)
const sql = `INSERT INTO ${TableName.File} (workspace_id, card_id, message_id, blob_id, type, filename, creator, created, message_created, size)
VALUES ($1::uuid, $2::varchar, $3::int8, $4::uuid, $5::varchar, $6::varchar, $7::varchar, $8::timestamptz, $9::timestamptz, $10::int8)`
await this.execute(
@@ -189,7 +179,7 @@ export class MessagesDb extends BaseDb {
db.filename,
db.creator,
db.created,
db.message_created_sec,
db.message_created,
db.size
],
'insert file'
@@ -210,6 +200,7 @@ export class MessagesDb extends BaseDb {
async createReaction(
card: CardID,
message: MessageID,
messageCreated: Date,
reaction: string,
creator: SocialID,
created: Date
@@ -238,13 +229,14 @@ export class MessagesDb extends BaseDb {
'insert reaction'
)
} else {
await this.createPatch(card, message, PatchType.addReaction, reaction, creator, created)
await this.createPatch(card, message, messageCreated, PatchType.addReaction, reaction, creator, created)
}
}
async removeReaction(
card: CardID,
message: MessageID,
messageCreated: Date,
reaction: string,
creator: SocialID,
created: Date
@@ -265,26 +257,33 @@ export class MessagesDb extends BaseDb {
AND creator = $5::varchar`
await this.execute(sql, [this.workspace, card, message, reaction, creator], 'remove reaction')
} else {
await this.createPatch(card, message, PatchType.removeReaction, reaction, creator, created)
await this.createPatch(card, message, messageCreated, PatchType.removeReaction, reaction, creator, created)
}
}
// Thread
async createThread(card: CardID, message: MessageID, thread: CardID, created: Date): Promise<void> {
async createThread(
card: CardID,
message: MessageID,
messageCreated: Date,
thread: CardID,
created: Date
): Promise<void> {
const db: ThreadDb = {
workspace_id: this.workspace,
card_id: card,
message_id: message,
message_created: messageCreated,
thread_id: thread,
replies_count: 0,
last_reply: created
}
const sql = `INSERT INTO ${TableName.Thread} (workspace_id, card_id, message_id, thread_id, replies_count,
last_reply)
VALUES ($1::uuid, $2::varchar, $3::bigint, $4::varchar, $5::int, $6::timestamptz)`
last_reply, message_created)
VALUES ($1::uuid, $2::varchar, $3::bigint, $4::varchar, $5::int, $6::timestamptz, $7::timestamptz)`
await this.execute(
sql,
[db.workspace_id, db.card_id, db.message_id, db.thread_id, db.replies_count, db.last_reply],
[db.workspace_id, db.card_id, db.message_id, db.thread_id, db.replies_count, db.last_reply, db.message_created],
'insert thread'
)
}
@@ -312,21 +311,21 @@ export class MessagesDb extends BaseDb {
}
// MessagesGroup
async createMessagesGroup(card: CardID, blobId: BlobID, fromSec: Date, toSec: Date, count: number): Promise<void> {
async createMessagesGroup(card: CardID, blobId: BlobID, fromDate: Date, toDate: Date, count: number): Promise<void> {
const db: MessagesGroupDb = {
workspace_id: this.workspace,
card_id: card,
blob_id: blobId,
from_sec: fromSec,
to_sec: toSec,
from_date: fromDate,
to_date: toDate,
count
}
const sql = `INSERT INTO ${TableName.MessagesGroup} (workspace_id, card_id, blob_id, from_sec, to_sec, count)
const sql = `INSERT INTO ${TableName.MessagesGroup} (workspace_id, card_id, blob_id, from_date, to_date, count)
VALUES ($1::uuid, $2::varchar, $3::uuid, $4::timestamptz, $5::timestamptz, $6::int)`
await this.execute(
sql,
[db.workspace_id, db.card_id, db.blob_id, db.from_sec, db.to_sec, db.count],
[db.workspace_id, db.card_id, db.blob_id, db.from_date, db.to_date, db.count],
'insert messages group'
)
}
@@ -388,7 +387,8 @@ export class MessagesDb extends BaseDb {
f.message_id,
jsonb_agg(jsonb_build_object(
'card_id', f.card_id,
'message_id', f.message_id,
'message_id', f.message_id::text,
'message_created', f.message_created,
'blob_id', f.blob_id,
'type', f.type,
'filename', f.filename,
@@ -414,7 +414,7 @@ export class MessagesDb extends BaseDb {
r.card_id,
r.message_id,
jsonb_agg(jsonb_build_object(
'message_id', r.message_id,
'message_id', r.message_id::text,
'reaction', r.reaction,
'creator', r.creator,
'created', r.created
@@ -484,7 +484,7 @@ export class MessagesDb extends BaseDb {
return `
SELECT
m.id,
m.id::text,
m.card_id,
m.type,
m.content,
@@ -546,7 +546,8 @@ export class MessagesDb extends BaseDb {
// Find thread
async findThread(thread: CardID): Promise<Thread | undefined> {
const sql = `SELECT t.card_id,
t.message_id,
t.message_id::text,
t.message_created,
t.thread_id,
t.replies_count,
t.last_reply
@@ -564,14 +565,15 @@ export class MessagesDb extends BaseDb {
const select = `
SELECT mg.card_id,
mg.blob_id,
mg.from_sec,
mg.to_sec,
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,
'message_id', p.message_id::text,
'message_created', p.message_created,
'type', p.type,
'content', p.content,
'creator', p.creator,
@@ -580,14 +582,14 @@ export class MessagesDb extends BaseDb {
FROM ${TableName.Patch} p
WHERE p.workspace_id = mg.workspace_id
AND p.card_id = mg.card_id
AND p.message_created_sec BETWEEN mg.from_sec AND mg.to_sec
AND p.message_created BETWEEN mg.from_date AND mg.to_date
) sub`
const { where, values } = this.buildMessagesGroupWhere(params)
const orderBy =
params.orderBy === 'toSec'
? `ORDER BY mg.to_sec ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}`
: `ORDER BY mg.from_sec ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}`
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'}`
const limit = params.limit != null ? ` LIMIT ${params.limit}` : ''
const sql = [select, where, orderBy, limit].join(' ')
@@ -613,14 +615,14 @@ export class MessagesDb extends BaseDb {
values.push(params.blobId)
}
const fromDateCondition = getCondition('mg', 'from_sec', index, params.fromSec, 'timestamptz')
const fromDateCondition = getCondition('mg', 'from_date', index, params.fromDate, 'timestamptz')
if (fromDateCondition != null) {
where.push(fromDateCondition.where)
values.push(...fromDateCondition.values)
index = fromDateCondition.index
}
const toDateCondition = getCondition('mg', 'to_sec', index, params.toSec, 'timestamptz')
const toDateCondition = getCondition('mg', 'to_date', index, params.toDate, 'timestamptz')
if (toDateCondition != null) {
where.push(toDateCondition.where)
values.push(...toDateCondition.values)
+16 -16
View File
@@ -46,7 +46,7 @@ export class NotificationsDb extends BaseDb {
const sqlValues = collaborators
.map((account, index) => {
const i = index * 3
const i = index * 5
values.push(this.workspace, card, account, date ?? new Date(), cardType)
return `($${i + 1}::uuid, $${i + 2}::varchar, $${i + 3}::uuid, $${i + 4}::timestamptz, $${i + 5}::varchar)`
})
@@ -104,7 +104,7 @@ export class NotificationsDb extends BaseDb {
}
const sql = `INSERT INTO ${TableName.Notification} (message_id, context_id, created)
VALUES ($1::bigint, $2::int8, $3::timestamptz)
RETURNING id`
RETURNING id::text`
const result = await this.execute(sql, [db.message_id, db.context_id, db.created], 'insert notification')
return result[0].id as NotificationID
}
@@ -131,7 +131,7 @@ export class NotificationsDb extends BaseDb {
}
const sql = `INSERT INTO ${TableName.NotificationContext} (workspace_id, card_id, account, last_view, last_update)
VALUES ($1::uuid, $2::varchar, $3::uuid, $4::timestamptz, $5::timestamptz)
RETURNING id`
RETURNING id::text`
const result = await this.execute(
sql,
[db.workspace_id, db.card_id, db.account, db.last_view, db.last_update],
@@ -142,8 +142,8 @@ export class NotificationsDb extends BaseDb {
async removeContext(context: ContextID, account: AccountID): Promise<void> {
const sql = `DELETE
FROM ${TableName.Notification}
WHERE context = $1::int8 AND account = $2::uuid`
FROM ${TableName.NotificationContext}
WHERE id = $1::int8 AND account = $2::uuid`
await this.execute(sql, [context, account], 'remove notification context')
}
@@ -183,9 +183,9 @@ export class NotificationsDb extends BaseDb {
let joinMessages = ''
let buildObject = `
JSONB_BUILD_OBJECT(
'id', n.id,
'id', n.id::text,
'created', n.created,
'message_id', n.message_id
'message_id', n.message_id::text
)`
if (withMessages) {
@@ -197,13 +197,13 @@ export class NotificationsDb extends BaseDb {
LEFT JOIN ${TableName.MessagesGroup} mg
ON nc.workspace_id = mg.workspace_id
AND nc.card_id = mg.card_id
AND n.created BETWEEN mg.from_sec AND mg.to_sec`
AND n.created BETWEEN mg.from_date AND mg.to_date`
buildObject = `
JSONB_BUILD_OBJECT(
'id', n.id,
'id', n.id::text,
'created', n.created,
'message_id', n.message_id,
'message_id', n.message_id::text,
'message_type', m.type,
'message_content', m.content,
'message_data', m.data,
@@ -211,8 +211,8 @@ export class NotificationsDb extends BaseDb {
'message_creator', m.creator,
'message_created', m.created,
'message_group_blob_id', mg.blob_id,
'message_group_from_sec', mg.from_sec,
'message_group_to_sec', mg.to_sec,
'message_group_from_date', mg.from_date,
'message_group_to_date', mg.to_date,
'message_group_count', mg.count,
'message_patches', (
SELECT COALESCE(
@@ -270,7 +270,7 @@ export class NotificationsDb extends BaseDb {
}
const sql = `
SELECT nc.id,
SELECT nc.id::text,
nc.card_id,
nc.account,
nc.last_view,
@@ -307,8 +307,8 @@ export class NotificationsDb extends BaseDb {
m.data AS message_data,
m.external_id AS message_external_id,
mg.blob_id AS message_group_blob_id,
mg.from_sec AS message_group_from_sec,
mg.to_sec AS message_group_to_sec,
mg.from_date AS message_group_from_date,
mg.to_date AS message_group_to_date,
mg.count AS message_group_count,
(SELECT json_agg(
jsonb_build_object(
@@ -330,7 +330,7 @@ export class NotificationsDb extends BaseDb {
LEFT JOIN ${TableName.MessagesGroup} mg
ON nc.workspace_id = mg.workspace_id
AND nc.card_id = mg.card_id
AND n.created BETWEEN mg.from_sec AND mg.to_sec
AND n.created BETWEEN mg.from_date AND mg.to_date
`
}
+5 -4
View File
@@ -58,8 +58,8 @@ export interface MessagesGroupDb {
workspace_id: WorkspaceID
card_id: CardID
blob_id: BlobID
from_sec: Date
to_sec: Date
from_date: Date
to_date: Date
count: number
patches?: PatchDb[]
}
@@ -72,7 +72,7 @@ export interface PatchDb {
content: RichText
creator: SocialID
created: Date
message_created_sec: Date
message_created: Date
}
export interface ReactionDb {
@@ -94,13 +94,14 @@ export interface FileDb {
type: string
creator: SocialID
created: Date
message_created_sec: Date
message_created: Date
}
export interface ThreadDb {
workspace_id: WorkspaceID
card_id: CardID
message_id: MessageID
message_created: Date
thread_id: CardID
replies_count: number
last_reply: Date
+102 -7
View File
@@ -57,10 +57,22 @@ export async function initSchema(sql: postgres.Sql) {
}
function getMigrations(): [string, string][] {
return [migrationO1(), migrationO2(), migrationO3(), migrationO4(), migrationO5()]
return [
migrationV1_1(),
migrationV2_1(),
migrationV3_1(),
migrationV4_1(),
migrationV4_2(),
migrationV5_1(),
migrationV5_2(),
migrationV5_3(),
migrationV5_4(),
migrationV5_5(),
migrationV5_6()
]
}
function migrationO1(): [string, string] {
function migrationV1_1(): [string, string] {
const sql = `
CREATE TABLE IF NOT EXISTS communication.messages
(
@@ -204,7 +216,7 @@ function migrationO1(): [string, string] {
return ['init_tables_01', sql]
}
function migrationO2(): [string, string] {
function migrationV2_1(): [string, string] {
const sql = `
CREATE TABLE IF NOT EXISTS communication.label
(
@@ -220,15 +232,15 @@ function migrationO2(): [string, string] {
return ['init_labels_02', sql]
}
function migrationO3(): [string, string] {
function migrationV3_1(): [string, string] {
const sql = `
ALTER TABLE communication.collaborators
ADD COLUMN IF NOT EXISTS card_type VARCHAR(255) NOT NULL DEFAULT 'card:class:Card';
ADD COLUMN IF NOT EXISTS card_type VARCHAR(255) NOT NULL DEFAULT 'card:class:Card';
`
return ['add_card_type_to_collaborators_03', sql]
}
function migrationO4(): [string, string] {
function migrationV4_1(): [string, string] {
const sql = `
ALTER TABLE communication.messages
ADD COLUMN IF NOT EXISTS external_id VARCHAR(255);
@@ -236,7 +248,7 @@ function migrationO4(): [string, string] {
return ['message_add_external_id_column', sql]
}
function migrationO5(): [string, string] {
function migrationV4_2(): [string, string] {
const sql = `
CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_unique_external_id
ON communication.messages (external_id)
@@ -244,3 +256,86 @@ function migrationO5(): [string, string] {
`
return ['message_add_external_id_column_unique_index', sql]
}
function migrationV5_1(): [string, string] {
const sql = `
ALTER TABLE communication.reactions
DROP CONSTRAINT IF EXISTS reactions_workspace_id_card_id_message_id_fkey;
`
return ['remove-reactions-fk_v5_1', sql]
}
function migrationV5_2(): [string, string] {
const sql = `
ALTER TABLE communication.messages
ALTER COLUMN id SET DEFAULT unique_rowid();
ALTER TABLE communication.messages
DROP CONSTRAINT IF EXISTS messages_pkey,
ADD CONSTRAINT messages_pkey PRIMARY KEY (id);`
return ['migrate-message-id_v5_2', sql]
}
function migrationV5_3(): [string, string] {
const sql = `
ALTER TABLE communication.messages_groups ADD COLUMN from_date TIMESTAMPTZ;
ALTER TABLE communication.messages_groups ADD COLUMN to_date TIMESTAMPTZ;
ALTER TABLE communication.patch ADD COLUMN message_created TIMESTAMPTZ;
ALTER TABLE communication.files ADD COLUMN message_created TIMESTAMPTZ;
ALTER TABLE communication.thread ADD COLUMN IF NOT EXISTS message_created TIMESTAMPTZ NOT NULL DEFAULT now();
DROP INDEX IF EXISTS communication.thread_workspace_id_card_id_message_id_key CASCADE;
ALTER TABLE communication.thread ADD CONSTRAINT thread_unique_constraint UNIQUE (message_id);
`
return ['add-date-columns_v5_3', sql]
}
function migrationV5_4(): [string, string] {
const sql = `
UPDATE communication.messages_groups
SET from_date = from_sec::TIMESTAMPTZ,
to_date = to_sec::TIMESTAMPTZ;
ALTER TABLE communication.messages_groups ALTER COLUMN from_date SET NOT NULL;
ALTER TABLE communication.messages_groups ALTER COLUMN to_date SET NOT NULL;
ALTER TABLE communication.messages_groups DROP COLUMN from_sec;
ALTER TABLE communication.messages_groups DROP COLUMN to_sec;
UPDATE communication.patch
SET message_created = message_created_sec::TIMESTAMPTZ;
ALTER TABLE communication.patch ALTER COLUMN message_created SET NOT NULL;
ALTER TABLE communication.patch DROP COLUMN message_created_sec;
UPDATE communication.files
SET message_created = message_created_sec::TIMESTAMPTZ;
ALTER TABLE communication.files ALTER COLUMN message_created SET NOT NULL;
ALTER TABLE communication.files DROP COLUMN message_created_sec;
`
return ['migrate-date-values_v5_4', sql]
}
function migrationV5_5(): [string, string] {
const sql = `
ALTER TABLE communication.reactions
DROP CONSTRAINT IF EXISTS reactions_pkey;
ALTER TABLE communication.reactions
ADD CONSTRAINT reactions_pkey PRIMARY KEY (message_id, creator, reaction);
`
return ['migrate-reactions-pk_v5_5', sql]
}
function migrationV5_6(): [string, string] {
const sql = `
ALTER TABLE communication.files
DROP CONSTRAINT IF EXISTS files_pkey;
ALTER TABLE communication.files
ADD CONSTRAINT files_pkey PRIMARY KEY (message_id, blob_id);
ALTER TABLE communication.reactions
ADD CONSTRAINT reactions_message_fkey FOREIGN KEY (message_id)
REFERENCES communication.messages (id) ON DELETE CASCADE;
`
return ['migrate-constraints_v5_6', sql]
}
+5 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@hcengineering/communication-query",
"version": "0.1.0",
"main": "src/index.ts",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "./types/index.d.ts",
"files": [
@@ -14,7 +14,8 @@
"build:types": "tsc --project ./tsconfig.json --emitDeclarationOnly --declarationDir ./types",
"build:browser": "bun build src/index.ts --outdir dist --target browser",
"lint": "eslint src/*.ts",
"format": "prettier --write src/**/*.ts "
"format": "prettier --write src/**/*.ts ",
"clean": "rm -rf dist && rm -rf types && rm -rf .turbo"
},
"devDependencies": {
"@types/bun": "^1.1.14",
@@ -25,7 +26,8 @@
"@hcengineering/communication-shared": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
"@hcengineering/communication-yaml": "workspace:*",
"fast-equals": "^5.0.1"
"fast-equals": "^5.0.1",
"uuid": "^11.1.0"
},
"peerDependencies": {
"typescript": "^5.6.3"
-1
View File
@@ -14,4 +14,3 @@
//
export * from './lq'
export type { QueryClient } from './types'
+3 -2
View File
@@ -16,6 +16,7 @@
import type { FindLabelsParams, Label, WorkspaceID } from '@hcengineering/communication-types'
import {
type EventResult,
type FindClient,
type LabelCreatedEvent,
type LabelRemovedEvent,
LabelResponseEventType,
@@ -25,7 +26,7 @@ import {
} from '@hcengineering/communication-sdk-types'
import { QueryResult } from '../result'
import { type Query, type QueryClient, type QueryId } from '../types'
import { type Query, type QueryId } from '../types'
function getId(label: Label): string {
return `${label.label}:${label.card}:${label.account}`
@@ -35,7 +36,7 @@ export class LabelsQuery implements Query<Label, FindLabelsParams> {
private result: Promise<QueryResult<Label>> | QueryResult<Label>
constructor(
private readonly client: QueryClient,
private readonly client: FindClient,
private readonly workspace: WorkspaceID,
private readonly filesUrl: string,
public readonly id: QueryId,
+4 -3
View File
@@ -30,10 +30,11 @@ import type {
QueryCallback,
RequestEvent,
EventResult,
PagedQueryCallback
PagedQueryCallback,
FindClient
} from '@hcengineering/communication-sdk-types'
import type { FindParams, QueryId, QueryClient, AnyQuery } from './types'
import type { FindParams, QueryId, AnyQuery } from './types'
import { MessagesQuery } from './messages/query'
import { NotificationQuery } from './notifications/query'
import { NotificationContextsQuery } from './notification-contexts/query'
@@ -51,7 +52,7 @@ export class LiveQueries {
private counter: number = 0
constructor(
private readonly client: QueryClient,
private readonly client: FindClient,
private readonly workspace: WorkspaceID,
private readonly filesUrl: string
) {
+40 -28
View File
@@ -45,18 +45,27 @@ import {
type CreateMessageResult,
MessageResponseEventType,
MessageRequestEventType,
type PagedQueryCallback
type PagedQueryCallback,
type FindClient
} from '@hcengineering/communication-sdk-types'
import { applyPatch, applyPatches, generateMessageId, parseMessageId } from '@hcengineering/communication-shared'
import { applyPatch, applyPatches } from '@hcengineering/communication-shared'
import { loadGroupFile } from '@hcengineering/communication-yaml'
import { v4 as uuid } from 'uuid'
import { QueryResult } from '../result'
import { defaultQueryParams, Direction, type PagedQuery, type QueryId, type QueryClient } from '../types'
import {
defaultQueryParams,
Direction,
type PagedQuery,
type QueryId,
type MessageQueryParams,
type OneMessageQueryParams
} from '../types'
import { WindowImpl } from '../window'
const GROUPS_LIMIT = 20
export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
export class MessagesQuery implements PagedQuery<Message, MessageQueryParams> {
private result: Promise<QueryResult<Message>> | QueryResult<Message>
private messagesFromFiles: Message[] = []
@@ -81,15 +90,15 @@ export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
private tmpMessages: Map<string, MessageID> = new Map()
constructor(
private readonly client: QueryClient,
private readonly client: FindClient,
private readonly workspace: WorkspaceID,
private readonly filesUrl: string,
public readonly id: QueryId,
public readonly params: FindMessagesParams,
public readonly params: MessageQueryParams,
private callback?: PagedQueryCallback<Message>,
initialResult?: QueryResult<Message>
) {
const baseLimit = params.id != null ? 1 : (this.params.limit ?? defaultQueryParams.limit)
const baseLimit = 'id' in params && params.id != null ? 1 : (this.params.limit ?? defaultQueryParams.limit)
this.limit = baseLimit + 1
this.params = {
...params,
@@ -176,7 +185,7 @@ export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
const eventId = event._id
if (eventId == null) return
const tmpId = generateMessageId()
const tmpId = uuid() as MessageID
let resultId: MessageID | undefined
const tmpMessage: Message = {
id: tmpId,
@@ -287,9 +296,9 @@ export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
}
private isInitLoadingForward(): boolean {
const { order, id } = this.params
const { order } = this.params
if (id != null) {
if (this.isOneMessageQuery(this.params)) {
return true
}
@@ -432,7 +441,7 @@ export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
? currentGroups
: await this.findGroups(
direction,
direction === Direction.Forward ? this.lastGroup?.fromSec : this.firstGroup?.fromSec
direction === Direction.Forward ? this.lastGroup?.fromDate : this.firstGroup?.fromDate
)
if (currentGroups.length === 0) {
@@ -447,7 +456,7 @@ export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
} else {
this.prev.hasGroups = groups.length >= GROUPS_LIMIT
}
if (this.params.id != null) {
if (this.isOneMessageQuery(this.params)) {
this.next.hasGroups = false
this.prev.hasGroups = false
}
@@ -495,8 +504,9 @@ export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
private matchFileMessages(file: ParsedFile, created?: Date): Message[] {
let result: Message[] = file.messages
if (this.params.id != null) {
const msg = file.messages.find((it) => it.id === this.params.id)
const params = this.params
if (this.isOneMessageQuery(params)) {
const msg = file.messages.find((it) => it.id === params.id)
result = msg != null ? [msg] : []
}
@@ -530,24 +540,22 @@ export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
}
}
private async findGroupByMessage(id: MessageID): Promise<MessagesGroup | undefined> {
const date = parseMessageId(id)
private async findGroupByDate(created: Date): Promise<MessagesGroup | undefined> {
const groups = await this.client.findMessagesGroups({
card: this.params.card,
fromSec: { lessOrEqual: date },
toSec: { greaterOrEqual: date },
fromDate: { lessOrEqual: created },
toDate: { greaterOrEqual: created },
limit: 1,
order: SortingOrder.Ascending,
orderBy: 'fromSec'
orderBy: 'fromDate'
})
return groups[0]
}
private async findGroups(direction: Direction, fromDate?: Date): Promise<MessagesGroup[]> {
if (this.params.id != null) {
const group = await this.findGroupByMessage(this.params.id)
if (this.isOneMessageQuery(this.params)) {
const group = await this.findGroupByDate(this.params.created)
return group !== undefined ? [group] : []
}
@@ -556,7 +564,7 @@ export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
card: this.params.card,
limit: GROUPS_LIMIT,
order: direction === Direction.Forward ? SortingOrder.Ascending : SortingOrder.Descending,
orderBy: 'fromSec'
orderBy: 'fromDate'
})
}
@@ -564,8 +572,8 @@ export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
card: this.params.card,
limit: GROUPS_LIMIT,
order: direction === Direction.Forward ? SortingOrder.Ascending : SortingOrder.Descending,
orderBy: 'fromSec',
fromSec:
orderBy: 'fromDate',
fromDate:
direction === Direction.Forward
? {
greater: fromDate
@@ -580,6 +588,10 @@ export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
return await this.client.findMessages(params, this.id)
}
private isOneMessageQuery(params: MessageQueryParams): params is OneMessageQueryParams {
return 'id' in this.params && this.params.id != null
}
private async notify(): Promise<void> {
if (this.callback == null) return
if (this.result instanceof Promise) this.result = await this.result
@@ -588,7 +600,7 @@ export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
}
private match(message: Message): boolean {
if (this.params.id != null && this.params.id !== message.id) {
if (this.isOneMessageQuery(this.params) && this.params.id !== message.id) {
return false
}
if (this.params.card !== message.card) {
@@ -655,8 +667,8 @@ export class MessagesQuery implements PagedQuery<Message, FindMessagesParams> {
if (this.result instanceof Promise) this.result = await this.result
const { patch } = event
const created = parseMessageId(patch.message)
const groups = this.groupsBuffer.filter((it) => it.fromSec <= created && it.toSec >= created)
const { messageCreated } = patch
const groups = this.groupsBuffer.filter((it) => it.fromDate <= messageCreated && it.toDate >= messageCreated)
for (const group of groups) {
if (group.patches != null) {
@@ -33,11 +33,12 @@ import {
type PatchCreatedEvent,
type PagedQueryCallback,
type RequestEvent,
type ResponseEvent
type ResponseEvent,
type FindClient
} from '@hcengineering/communication-sdk-types'
import { applyPatch } from '@hcengineering/communication-shared'
import { defaultQueryParams, type PagedQuery, type QueryId, type QueryClient } from '../types'
import { defaultQueryParams, type PagedQuery, type QueryId } from '../types'
import { QueryResult } from '../result'
import { WindowImpl } from '../window'
import { loadMessageFromGroup } from '../utils'
@@ -50,7 +51,7 @@ export class NotificationContextsQuery implements PagedQuery<NotificationContext
private backward: Promise<NotificationContext[]> | NotificationContext[] = []
constructor(
private readonly client: QueryClient,
private readonly client: FindClient,
private readonly workspace: WorkspaceID,
private readonly filesUrl: string,
public readonly id: QueryId,
+3 -2
View File
@@ -20,6 +20,7 @@ import {
type WorkspaceID
} from '@hcengineering/communication-types'
import {
type FindClient,
type NotificationContextRemovedEvent,
type NotificationContextUpdatedEvent,
type NotificationCreatedEvent,
@@ -30,7 +31,7 @@ import {
type ResponseEvent
} from '@hcengineering/communication-sdk-types'
import { defaultQueryParams, type PagedQuery, type QueryId, type QueryClient } from '../types'
import { defaultQueryParams, type PagedQuery, type QueryId } from '../types'
import { QueryResult } from '../result'
import { WindowImpl } from '../window'
import { loadMessageFromGroup } from '../utils'
@@ -39,7 +40,7 @@ export class NotificationQuery implements PagedQuery<Notification, FindNotificat
private result: QueryResult<Notification> | Promise<QueryResult<Notification>>
constructor(
private readonly client: QueryClient,
private readonly client: FindClient,
private readonly workspace: WorkspaceID,
private readonly filesUrl: string,
public readonly id: QueryId,
+18 -24
View File
@@ -16,17 +16,10 @@
import { type ResponseEvent } from '@hcengineering/communication-sdk-types'
import {
SortingOrder,
type FindMessagesGroupsParams,
type FindMessagesParams,
type FindNotificationContextParams,
type FindNotificationsParams,
type Message,
type MessagesGroup,
type NotificationContext,
type Window,
type Notification,
type Label,
type FindLabelsParams
type ComparisonOperator,
type CardID,
type MessageID
} from '@hcengineering/communication-types'
import type { EventResult, RequestEvent } from '@hcengineering/communication-sdk-types'
@@ -74,20 +67,21 @@ export interface Query<R = any, P = FindParams> extends BaseQuery<R, P> {
export type AnyQuery = Query | PagedQuery
export interface QueryClient {
onEvent(event: ResponseEvent): void
export interface DefaultMessageQueryParams {
card: CardID
onRequest(event: RequestEvent, promise: Promise<EventResult>): void
limit?: number
order?: SortingOrder
findMessages(params: FindMessagesParams, queryId?: number): Promise<Message[]>
findMessagesGroups(params: FindMessagesGroupsParams): Promise<MessagesGroup[]>
findNotificationContexts(params: FindNotificationContextParams, queryId?: number): Promise<NotificationContext[]>
findNotifications(params: FindNotificationsParams, queryId?: number): Promise<Notification[]>
findLabels(params: FindLabelsParams, queryId?: number): Promise<Label[]>
unsubscribeQuery(id: number): Promise<void>
files?: boolean
reactions?: boolean
replies?: boolean
created?: Partial<Record<ComparisonOperator, Date>> | Date
}
export interface OneMessageQueryParams extends DefaultMessageQueryParams {
id: MessageID
created: Date
}
export type MessageQueryParams = OneMessageQueryParams | DefaultMessageQueryParams
+3 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@hcengineering/communication-rest-client",
"version": "0.1.0",
"main": "src/index.ts",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "./types/index.d.ts",
"files": [
@@ -15,7 +15,8 @@
"build:browser": "bun build src/index.ts --outdir dist --target browser",
"build:node": "bun build src/index.ts --target node --format cjs --outfile dist/index.cjs",
"lint": "eslint src/*.ts",
"format": "prettier --write src/**/*.ts "
"format": "prettier --write src/**/*.ts ",
"clean": "rm -rf dist && rm -rf types && rm -rf .turbo"
},
"devDependencies": {
"@types/bun": "^1.1.14",
+24 -6
View File
@@ -101,8 +101,9 @@ class RestClientImpl implements RestClient {
content: RichText,
creator: SocialID,
type: MessageType,
data?: MessageData
): Promise<MessageID> {
data?: MessageData,
created?: Date
): Promise<CreateMessageResult> {
const result = await this.event({
type: MessageRequestEventType.CreateMessage,
messageType: type,
@@ -110,15 +111,23 @@ class RestClientImpl implements RestClient {
cardType,
content,
creator,
data
data,
created
})
return (result as CreateMessageResult).id
return result as CreateMessageResult
}
async updateMessage(card: CardID, message: MessageID, content: RichText, creator: SocialID): Promise<void> {
async updateMessage(
card: CardID,
message: MessageID,
messageCreated: Date,
content: RichText,
creator: SocialID
): Promise<void> {
await this.event({
type: MessageRequestEventType.CreatePatch,
patchType: PatchType.update,
messageCreated,
card,
message,
content,
@@ -143,6 +152,7 @@ class RestClientImpl implements RestClient {
async createFile(
card: CardID,
message: MessageID,
messageCreated: Date,
blobId: BlobID,
fileType: string,
filename: string,
@@ -153,6 +163,7 @@ class RestClientImpl implements RestClient {
type: MessageRequestEventType.CreateFile,
card,
message,
messageCreated,
blobId,
fileType,
filename,
@@ -161,11 +172,18 @@ class RestClientImpl implements RestClient {
})
}
async removeFile(card: CardID, message: MessageID, blobId: BlobID, creator: SocialID): Promise<void> {
async removeFile(
card: CardID,
message: MessageID,
messageCreated: Date,
blobId: BlobID,
creator: SocialID
): Promise<void> {
await this.event({
type: MessageRequestEventType.RemoveFile,
card,
message,
messageCreated,
blobId,
creator
})
+17 -4
View File
@@ -13,7 +13,7 @@
// limitations under the License.
//
import type { EventResult, RequestEvent } from '@hcengineering/communication-sdk-types'
import type { CreateMessageResult, EventResult, RequestEvent } from '@hcengineering/communication-sdk-types'
import type {
FindMessagesGroupsParams,
FindMessagesParams,
@@ -48,19 +48,32 @@ export interface RestClient {
creator: SocialID,
type: MessageType,
data?: MessageData
) => Promise<MessageID>
updateMessage: (card: CardID, message: MessageID, content: RichText, creator: SocialID) => Promise<void>
) => Promise<CreateMessageResult>
updateMessage: (
card: CardID,
message: MessageID,
messageCreated: Date,
content: RichText,
creator: SocialID
) => Promise<void>
removeMessage: (card: CardID, message: MessageID) => Promise<MessageID | undefined>
removeMessages: (card: CardID, messages: MessageID[]) => Promise<MessageID[]>
createFile: (
card: CardID,
message: MessageID,
messageCreated: Date,
blobId: BlobID,
fileType: string,
filename: string,
size: number,
creator: SocialID
) => Promise<void>
removeFile: (card: CardID, message: MessageID, blobId: BlobID, creator: SocialID) => Promise<void>
removeFile: (
card: CardID,
message: MessageID,
messageCreated: Date,
blobId: BlobID,
creator: SocialID
) => Promise<void>
}
+2 -1
View File
@@ -15,7 +15,8 @@
"build:browser": "bun build src/index.ts --outdir dist --target browser",
"build:node": "bun build src/index.ts --target node --format cjs --outfile dist/index.cjs",
"lint": "eslint src/*.ts",
"format": "prettier --write src/**/*.ts"
"format": "prettier --write src/**/*.ts",
"clean": "rm -rf dist && rm -rf types && rm -rf .turbo"
},
"devDependencies": {
"@types/bun": "^1.1.14"
+15 -39
View File
@@ -14,58 +14,34 @@
//
import type {
CardID,
ContextID,
FindLabelsParams,
FindMessagesGroupsParams,
FindMessagesParams,
FindNotificationContextParams,
FindNotificationsParams,
Label,
Message,
MessageID,
NotificationContext,
RichText,
SocialID,
Notification,
FindMessagesGroupsParams,
MessagesGroup,
BlobID
Notification,
NotificationContext
} from '@hcengineering/communication-types'
import type { FindMessagesParams } from '@hcengineering/communication-types'
import type { ResponseEvent } from './event'
export interface Client {
createMessage(card: CardID, content: RichText, creator: SocialID): Promise<MessageID>
removeMessage(card: CardID, id: MessageID): Promise<void>
updateMessage(card: CardID, message: MessageID, content: RichText, creator: SocialID): Promise<void>
createReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID): Promise<void>
removeReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID): Promise<void>
createFile(
card: CardID,
message: MessageID,
blobId: BlobID,
fileType: string,
filename: string,
creator: SocialID
): Promise<void>
removeFile(card: CardID, message: MessageID, blobId: BlobID): Promise<void>
createThread(card: CardID, message: MessageID, thread: CardID, created: Date): Promise<void>
createNotification(message: MessageID, context: ContextID): Promise<void>
removeNotification(message: MessageID, context: ContextID): Promise<void>
createNotificationContext(card: CardID, lastView: Date, lastUpdate: Date): Promise<ContextID>
removeNotificationContext(context: ContextID): Promise<void>
updateNotificationContext(context: ContextID, lastView?: Date, lastUpdate?: Date): Promise<void>
import type { EventResult, RequestEvent, ResponseEvent } from './event'
export interface FindClient {
onEvent(event: ResponseEvent): void
onRequest(event: RequestEvent, promise: Promise<EventResult>): void
findMessages(params: FindMessagesParams, queryId?: number): Promise<Message[]>
findMessagesGroups(params: FindMessagesGroupsParams): Promise<MessagesGroup[]>
findNotificationContexts(params: FindNotificationContextParams, queryId?: number): Promise<NotificationContext[]>
findNotifications(params: FindNotificationsParams, queryId?: number): Promise<Notification[]>
findLabels(params: FindLabelsParams, queryId?: number): Promise<Label[]>
unsubscribeQuery(id: number): Promise<void>
close(): void
}
+19 -4
View File
@@ -57,21 +57,36 @@ export interface DbAdapter {
createPatch(
card: CardID,
message: MessageID,
messageCreated: Date,
type: PatchType,
content: RichText,
creator: SocialID,
created: Date
): Promise<void>
createMessagesGroup(card: CardID, blobId: BlobID, fromSec: Date, toSec: Date, count: number): Promise<void>
createMessagesGroup(card: CardID, blobId: BlobID, fromDate: Date, toDate: Date, count: number): Promise<void>
removeMessagesGroup(card: CardID, blobId: BlobID): Promise<void>
createReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID, created: Date): Promise<void>
removeReaction(card: CardID, message: MessageID, reaction: string, creator: SocialID): Promise<void>
createReaction(
card: CardID,
message: MessageID,
messageCreated: Date,
reaction: string,
creator: SocialID,
created: Date
): Promise<void>
removeReaction(
card: CardID,
message: MessageID,
messageCreated: Date,
reaction: string,
creator: SocialID
): Promise<void>
createFile(
card: CardID,
message: MessageID,
messageCreated: Date,
blobId: BlobID,
fileType: string,
filename: string,
@@ -81,7 +96,7 @@ export interface DbAdapter {
): Promise<void>
removeFile(card: CardID, message: MessageID, blobId: BlobID): Promise<void>
createThread(card: CardID, message: MessageID, thread: CardID, created: Date): Promise<void>
createThread(card: CardID, message: MessageID, messageCreated: Date, thread: CardID, created: Date): Promise<void>
updateThread(thread: CardID, op: 'increment' | 'decrement', lastReply?: Date): Promise<void>
findMessages(params: FindMessagesParams): Promise<Message[]>
@@ -69,6 +69,7 @@ export interface CreateMessageEvent extends BaseRequestEvent {
creator: SocialID
data?: MessageData
externalId?: string
created?: Date
}
export interface RemoveMessagesEvent extends BaseRequestEvent {
@@ -82,6 +83,7 @@ export interface CreatePatchEvent extends BaseRequestEvent {
patchType: PatchType
card: CardID
message: MessageID
messageCreated: Date
content: RichText
creator: SocialID
}
@@ -90,6 +92,7 @@ export interface CreateReactionEvent extends BaseRequestEvent {
type: MessageRequestEventType.CreateReaction
card: CardID
message: MessageID
messageCreated: Date
reaction: string
creator: SocialID
}
@@ -98,6 +101,7 @@ export interface RemoveReactionEvent extends BaseRequestEvent {
type: MessageRequestEventType.RemoveReaction
card: CardID
message: MessageID
messageCreated: Date
reaction: string
creator: SocialID
}
@@ -106,6 +110,7 @@ export interface CreateFileEvent extends BaseRequestEvent {
type: MessageRequestEventType.CreateFile
card: CardID
message: MessageID
messageCreated: Date
blobId: BlobID
size: number
fileType: string
@@ -117,6 +122,7 @@ export interface RemoveFileEvent extends BaseRequestEvent {
type: MessageRequestEventType.RemoveFile
card: CardID
message: MessageID
messageCreated: Date
blobId: BlobID
creator: SocialID
}
@@ -125,6 +131,7 @@ export interface CreateThreadEvent extends BaseRequestEvent {
type: MessageRequestEventType.CreateThread
card: CardID
message: MessageID
messageCreated: Date
thread: CardID
}
@@ -150,6 +157,7 @@ export type MessageEventResult = CreateMessageResult | RemoveMessagesResult
export interface CreateMessageResult {
id: MessageID
created: Date
}
export interface RemoveMessagesResult {
@@ -102,6 +102,7 @@ export interface FileRemovedEvent extends BaseResponseEvent {
type: MessageResponseEventType.FileRemoved
card: CardID
message: MessageID
messageCreated: Date
blobId: BlobID
creator: SocialID
}
+3 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@hcengineering/communication-server",
"version": "0.1.0",
"main": "src/index.ts",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "./types/index.d.ts",
"files": [
@@ -14,7 +14,8 @@
"build:types": "tsc --project ./tsconfig.json --emitDeclarationOnly --declarationDir ./types",
"build:node": "bun build src/index.ts --target node --format cjs --outfile dist/index.js",
"lint": "eslint src/*.ts",
"format": "prettier --write src/**/*.ts "
"format": "prettier --write src/**/*.ts",
"clean": "rm -rf dist && rm -rf types && rm -rf .turbo"
},
"devDependencies": {
"@types/bun": "^1.1.14",
+11
View File
@@ -31,4 +31,15 @@ export class ApiError extends Error {
static forbidden(message: string): ApiError {
return new ApiError(403, `Forbidden: ${message}`)
}
toJSON() {
return {
code: this.code,
message: this.message
}
}
toString(): string {
return JSON.stringify(this.toJSON())
}
}
+55
View File
@@ -0,0 +1,55 @@
//
// 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 EventResult,
MessageRequestEventType,
type RequestEvent,
type SessionData
} from '@hcengineering/communication-sdk-types'
import { systemAccountUuid } from '@hcengineering/core'
import type { Middleware, MiddlewareContext } from '../types'
import { BaseMiddleware } from './base'
export class DateMiddleware extends BaseMiddleware implements Middleware {
constructor(
readonly context: MiddlewareContext,
next?: Middleware
) {
super(context, next)
}
async event(session: SessionData, event: RequestEvent, derived: boolean): Promise<EventResult> {
if (derived) return await this.provideEvent(session, event, derived)
switch (event.type) {
case MessageRequestEventType.CreateMessage: {
if (!this.isSystem(session)) {
delete event.created
}
break
}
default:
break
}
return this.provideEvent(session, event, derived)
}
private isSystem(session: SessionData): boolean {
const account = session.account
return systemAccountUuid === account.uuid
}
}
+35 -10
View File
@@ -32,6 +32,7 @@ import {
type CreateFileEvent,
type CreateLabelEvent,
type CreateMessageEvent,
type CreateMessageResult,
type CreateMessagesGroupEvent,
type CreateNotificationContextEvent,
type CreateNotificationEvent,
@@ -211,7 +212,7 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
}
private async createMessage(event: CreateMessageEvent): Promise<Result> {
const created = new Date()
const created = event.created ?? new Date()
const id = await this.db.createMessage(
event.card,
event.messageType,
@@ -239,18 +240,31 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
cardType: event.cardType,
message
}
const result: CreateMessageResult = {
id,
created
}
return {
responseEvent,
result: { id }
result
}
}
private async createPatch(event: CreatePatchEvent): Promise<Result> {
const created = new Date()
await this.db.createPatch(event.card, event.message, event.patchType, event.content, event.creator, created)
await this.db.createPatch(
event.card,
event.message,
event.messageCreated,
event.patchType,
event.content,
event.creator,
created
)
const patch: Patch = {
type: event.patchType,
messageCreated: event.messageCreated,
message: event.message,
content: event.content,
creator: event.creator,
@@ -290,7 +304,14 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
private async createReaction(event: CreateReactionEvent): Promise<Result> {
const created = new Date()
await this.db.createReaction(event.card, event.message, event.reaction, event.creator, created)
await this.db.createReaction(
event.card,
event.message,
event.messageCreated,
event.reaction,
event.creator,
created
)
const reaction: Reaction = {
message: event.message,
@@ -310,7 +331,7 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
}
private async removeReaction(event: RemoveReactionEvent): Promise<Result> {
await this.db.removeReaction(event.card, event.message, event.reaction, event.creator)
await this.db.removeReaction(event.card, event.message, event.messageCreated, event.reaction, event.creator)
const responseEvent: ReactionRemovedEvent = {
_id: event._id,
type: MessageResponseEventType.ReactionRemoved,
@@ -329,6 +350,7 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
await this.db.createFile(
event.card,
event.message,
event.messageCreated,
event.blobId,
event.fileType,
event.filename,
@@ -343,6 +365,7 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
file: {
card: event.card,
message: event.message,
messageCreated: event.messageCreated,
blobId: event.blobId,
type: event.fileType,
filename: event.filename,
@@ -363,6 +386,7 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
type: MessageResponseEventType.FileRemoved,
card: event.card,
message: event.message,
messageCreated: event.messageCreated,
blobId: event.blobId,
creator: event.creator
}
@@ -454,8 +478,8 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
}
async createMessagesGroup(event: CreateMessagesGroupEvent): Promise<Result> {
const { fromSec, toSec, count, card, blobId } = event.group
await this.db.createMessagesGroup(card, blobId, fromSec, toSec, count)
const { fromDate, toDate, count, card, blobId } = event.group
await this.db.createMessagesGroup(card, blobId, fromDate, toDate, count)
const responseEvent: MessagesGroupCreatedEvent = {
_id: event._id,
@@ -463,8 +487,8 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
group: {
card,
blobId,
fromSec,
toSec,
fromDate,
toDate,
count
}
}
@@ -481,7 +505,7 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
private async createThread(event: CreateThreadEvent): Promise<Result> {
const date = new Date()
await this.db.createThread(event.card, event.message, event.thread, date)
await this.db.createThread(event.card, event.message, event.messageCreated, event.thread, date)
const responseEvent: ThreadCreatedEvent = {
_id: event._id,
type: MessageResponseEventType.ThreadCreated,
@@ -489,6 +513,7 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
card: event.card,
thread: event.thread,
message: event.message,
messageCreated: event.messageCreated,
repliesCount: 0,
lastReply: date
}
+22 -14
View File
@@ -48,6 +48,7 @@ export class ValidateMiddleware extends BaseMiddleware implements Middleware {
const validationResult = schema.safeParse(data)
if (!validationResult.success) {
const errors = validationResult.error.errors.map((err) => err.message)
this.context.ctx.error(validationResult.error.message, data)
throw ApiError.badRequest(errors.join(', '))
}
}
@@ -171,9 +172,10 @@ const PatchType = z.string()
const RichText = z.string()
const SocialID = z.string()
const SortingOrder = z.number()
const Date = z.union([z.date(), z.string()])
// Find params
const dateOrRecordSchema = z.union([z.date(), z.record(z.date())])
const dateOrRecordSchema = z.union([z.date(), z.string(), z.record(z.string())])
const FindParamsSchema = z
.object({
@@ -196,9 +198,9 @@ const FindMessagesGroupsParamsSchema = FindParamsSchema.extend({
card: CardID,
blobId: BlobID.optional(),
patches: z.boolean().optional(),
fromSec: dateOrRecordSchema.optional(),
toSec: dateOrRecordSchema.optional(),
orderBy: z.enum(['fromSec', 'toSec']).optional()
fromDate: dateOrRecordSchema.optional(),
toDate: dateOrRecordSchema.optional(),
orderBy: z.enum(['fromDate', 'toDate']).optional()
}).strict()
const FindNotificationContextParamsSchema = FindParamsSchema.extend({
@@ -264,7 +266,8 @@ const CreateMessageEventSchema = BaseRequestEventSchema.extend({
content: RichText,
creator: SocialID,
data: MessageData.optional(),
externalId: z.string().optional()
externalId: z.string().optional(),
created: Date.optional()
}).strict()
const RemoveMessagesEventSchema = BaseRequestEventSchema.extend({
@@ -278,6 +281,7 @@ const CreatePatchEventSchema = BaseRequestEventSchema.extend({
patchType: PatchType,
card: CardID,
message: MessageID,
messageCreated: Date,
content: RichText,
creator: SocialID
}).strict()
@@ -286,6 +290,7 @@ const CreateReactionEventSchema = BaseRequestEventSchema.extend({
type: z.literal(MessageRequestEventType.CreateReaction),
card: CardID,
message: MessageID,
messageCreated: Date,
reaction: z.string(),
creator: SocialID
}).strict()
@@ -294,6 +299,7 @@ const RemoveReactionEventSchema = BaseRequestEventSchema.extend({
type: z.literal(MessageRequestEventType.RemoveReaction),
card: CardID,
message: MessageID,
messageCreated: Date,
reaction: z.string(),
creator: SocialID
}).strict()
@@ -302,6 +308,7 @@ const CreateFileEventSchema = BaseRequestEventSchema.extend({
type: z.literal(MessageRequestEventType.CreateFile),
card: CardID,
message: MessageID,
messageCreated: Date,
blobId: BlobID,
size: z.number(),
fileType: z.string(),
@@ -313,7 +320,7 @@ const RemoveFileEventSchema = BaseRequestEventSchema.extend({
type: z.literal(MessageRequestEventType.RemoveFile),
card: CardID,
message: MessageID,
blobId: BlobID,
messageCreated: Date,
creator: SocialID
}).strict()
@@ -321,6 +328,7 @@ const CreateThreadEventSchema = BaseRequestEventSchema.extend({
type: z.literal(MessageRequestEventType.CreateThread),
card: CardID,
message: MessageID,
messageCreated: Date,
thread: CardID
}).strict()
@@ -328,7 +336,7 @@ const UpdateThreadEventSchema = BaseRequestEventSchema.extend({
type: z.literal(MessageRequestEventType.UpdateThread),
thread: CardID,
replies: z.enum(['increment', 'decrement']),
lastReply: z.date().optional()
lastReply: Date.optional()
}).strict()
const CreateMessagesGroupEventSchema = BaseRequestEventSchema.extend({
@@ -348,7 +356,7 @@ const CreateNotificationEventSchema = BaseRequestEventSchema.extend({
type: z.literal(NotificationRequestEventType.CreateNotification),
context: ContextID,
message: MessageID,
created: z.date(),
created: Date,
account: AccountID
}).strict()
@@ -356,15 +364,15 @@ const RemoveNotificationsEventSchema = BaseRequestEventSchema.extend({
type: z.literal(NotificationRequestEventType.RemoveNotifications),
context: ContextID,
account: AccountID,
untilDate: z.date()
untilDate: Date
}).strict()
const CreateNotificationContextEventSchema = BaseRequestEventSchema.extend({
type: z.literal(NotificationRequestEventType.CreateNotificationContext),
card: CardID,
account: AccountID,
lastView: z.date(),
lastUpdate: z.date()
lastView: Date,
lastUpdate: Date
}).strict()
const RemoveNotificationContextEventSchema = BaseRequestEventSchema.extend({
@@ -377,8 +385,8 @@ const UpdateNotificationContextEventSchema = BaseRequestEventSchema.extend({
type: z.literal(NotificationRequestEventType.UpdateNotificationContext),
context: ContextID,
account: AccountID,
lastView: z.date().optional(),
lastUpdate: z.date().optional()
lastView: Date.optional(),
lastUpdate: Date.optional()
}).strict()
const AddCollaboratorsEventSchema = BaseRequestEventSchema.extend({
@@ -386,7 +394,7 @@ const AddCollaboratorsEventSchema = BaseRequestEventSchema.extend({
card: CardID,
cardType: CardType,
collaborators: z.array(AccountID).nonempty(),
date: z.date().optional()
date: Date.optional()
}).strict()
const RemoveCollaboratorsEventSchema = BaseRequestEventSchema.extend({
+2
View File
@@ -42,6 +42,7 @@ import { DatabaseMiddleware } from './middleware/db'
import { BroadcastMiddleware } from './middleware/broadcast'
import { createTriggersDb, TriggersMiddleware } from './middleware/triggers'
import { ValidateMiddleware } from './middleware/validate'
import { DateMiddleware } from './middleware/date'
export async function buildMiddlewares(
ctx: MeasureContext,
@@ -53,6 +54,7 @@ export async function buildMiddlewares(
const createFns: MiddlewareCreateFn[] = [
async (context, next) => new ValidateMiddleware(context, next),
async (context, next) => new PermissionsMiddleware(context, next),
async (context, next) => new DateMiddleware(context, next),
async (context, next) => new BroadcastMiddleware(broadcast, context, next),
async (context, next) => new DatabaseMiddleware(db, context, next),
async (context, next) => new TriggersMiddleware(createTriggersDb(db), context, next)
+5 -1
View File
@@ -52,6 +52,7 @@ async function onMessagesRemoved(ctx: TriggerCtx, event: MessagesRemovedEvent):
patchType: PatchType.removeReply,
card: thread.card,
message: thread.message,
messageCreated: thread.messageCreated,
content: thread.thread,
creator: socialId
}
@@ -70,7 +71,7 @@ async function onFileCreated(ctx: TriggerCtx, event: FileCreatedEvent): Promise<
if (message !== undefined) return []
const { file } = event
const patchContent: Omit<File, 'card' | 'message' | 'created' | 'creator'> = {
const patchContent: Omit<File, 'card' | 'message' | 'created' | 'creator' | 'messageCreated'> = {
blobId: file.blobId,
type: file.type,
filename: file.filename,
@@ -83,6 +84,7 @@ async function onFileCreated(ctx: TriggerCtx, event: FileCreatedEvent): Promise<
patchType: PatchType.addFile,
card: event.card,
message: file.message,
messageCreated: file.messageCreated,
content: JSON.stringify(patchContent),
creator: file.creator
}
@@ -100,6 +102,7 @@ async function onFileRemoved(ctx: TriggerCtx, event: FileRemovedEvent): Promise<
patchType: PatchType.removeFile,
card: event.card,
message: event.message,
messageCreated: event.messageCreated,
content: JSON.stringify({ blobId }),
creator: event.creator
}
@@ -164,6 +167,7 @@ async function addThreadReply(ctx: TriggerCtx, event: MessageCreatedEvent): Prom
patchType: PatchType.addReply,
card: thread.card,
message: thread.message,
messageCreated: thread.messageCreated,
content: thread.thread,
creator: message.creator
},
+5 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@hcengineering/communication-shared",
"version": "0.1.0",
"main": "src/index.ts",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "./types/index.d.ts",
"files": [
@@ -15,7 +15,8 @@
"build:browser": "bun build src/index.ts --outdir dist --target browser",
"build:node": "bun build src/index.ts --target node --format cjs --outfile dist/index.cjs",
"lint": "eslint src/*.ts",
"format": "prettier --write src/**/*.ts "
"format": "prettier --write src/**/*.ts ",
"clean": "rm -rf dist && rm -rf types && rm -rf .turbo"
},
"devDependencies": {
"@types/bun": "^1.1.14"
@@ -24,7 +25,8 @@
"typescript": "^5.6.3"
},
"dependencies": {
"@hcengineering/communication-types": "workspace:*"
"@hcengineering/communication-types": "workspace:*",
"@hcengineering/communication-sdk-types": "workspace:*"
},
"repository": {
"type": "git",
-1
View File
@@ -13,6 +13,5 @@
// limitations under the License.
//
export * from './message'
export * from './retry'
export * from './patch'
-38
View File
@@ -1,38 +0,0 @@
//
// 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 { MessageID } from '@hcengineering/communication-types'
let lastTimestamp = 0n
let counter = 0n
export function generateMessageId(): MessageID {
const timestamp = BigInt(Math.floor(Date.now() / 1000))
if (timestamp !== lastTimestamp) {
lastTimestamp = timestamp
counter = 0n
}
counter++
const id = (timestamp << 20n) | (counter << 10n) | BigInt(Math.floor(Math.random() * 1024))
return id.toString() as MessageID
}
export function parseMessageId(messageId: MessageID): Date {
return new Date(Number(BigInt(messageId) >> 20n) * 1000)
}
+9 -1
View File
@@ -88,6 +88,7 @@ function addReply(message: Message, thread: CardID, created: Date): Message {
thread: {
card: message.card,
message: message.id,
messageCreated: message.created,
thread,
repliesCount: 1,
lastReply: created
@@ -108,7 +109,14 @@ function addReply(message: Message, thread: CardID, created: Date): Message {
}
function addFile(message: Message, file: PatchFile, created: Date, creator: SocialID): Message {
message.files.push({ ...file, card: message.card, message: message.id, created, creator })
message.files.push({
...file,
card: message.card,
message: message.id,
created,
creator,
messageCreated: message.created
})
return message
}
+3 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@hcengineering/communication-types",
"version": "0.1.0",
"main": "src/index.ts",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "./types/index.d.ts",
"files": [
@@ -15,7 +15,8 @@
"build:browser": "bun build src/index.ts --outdir dist --target browser",
"build:node": "bun build src/index.ts --target node --format cjs --outfile dist/index.cjs",
"lint": "eslint src/*.ts",
"format": "prettier --write src/**/*.ts "
"format": "prettier --write src/**/*.ts ",
"clean": "rm -rf dist && rm -rf types && rm -rf .turbo"
},
"devDependencies": {
"@types/bun": "^1.1.14"
+5 -2
View File
@@ -79,14 +79,15 @@ export interface ActivityAttributeUpdate {
export interface MessagesGroup {
card: CardID
blobId: BlobID
fromSec: Date
toSec: Date
fromDate: Date
toDate: Date
count: number
patches?: Patch[]
}
export interface Patch {
message: MessageID
messageCreated: Date
type: PatchType
content: string
creator: SocialID
@@ -113,6 +114,7 @@ export interface Reaction {
export interface File {
card: CardID
message: MessageID
messageCreated: Date
blobId: BlobID
type: string
filename: string
@@ -124,6 +126,7 @@ export interface File {
export interface Thread {
card: CardID
message: MessageID
messageCreated: Date
thread: CardID
repliesCount: number
lastReply: Date
+3 -3
View File
@@ -55,9 +55,9 @@ export interface FindMessagesGroupsParams extends FindParams {
card: CardID
blobId?: BlobID
patches?: boolean
fromSec?: Partial<Record<ComparisonOperator, Date>> | Date
toSec?: Partial<Record<ComparisonOperator, Date>> | Date
orderBy?: 'fromSec' | 'toSec'
fromDate?: Partial<Record<ComparisonOperator, Date>> | Date
toDate?: Partial<Record<ComparisonOperator, Date>> | Date
orderBy?: 'fromDate' | 'toDate'
}
export interface FindNotificationContextParams extends FindParams {
+3 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@hcengineering/communication-yaml",
"version": "0.1.0",
"main": "src/index.ts",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "./types/index.d.ts",
"files": [
@@ -15,7 +15,8 @@
"build:browser": "bun build src/index.ts --outdir dist --target browser",
"build:node": "bun build src/index.ts --target node --format cjs --outfile dist/index.cjs",
"lint": "eslint src/*.ts",
"format": "prettier --write src/**/*.ts "
"format": "prettier --write src/**/*.ts ",
"clean": "rm -rf dist && rm -rf types && rm -rf .turbo"
},
"devDependencies": {
"@types/bun": "^1.1.14",
+2
View File
@@ -75,6 +75,7 @@ export function parseYaml(data: string): ParsedFile {
? {
card: metadata.card,
message: message.id,
messageCreated: message.created,
thread: message.thread.thread,
repliesCount: message.thread.repliesCount,
lastReply: message.thread.lastReply
@@ -83,6 +84,7 @@ export function parseYaml(data: string): ParsedFile {
files: message.files.map((file) => ({
...file,
message: message.id,
messageCreated: message.created,
card: metadata.card
})),
reactions: message.reactions.map((reaction) => ({
+65
View File
@@ -0,0 +1,65 @@
//
// 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 { FindClient } from '@hcengineering/communication-sdk-types'
import {
type CardID,
type Message,
type MessageID,
type MessagesGroup,
SortingOrder,
type WorkspaceID
} from '@hcengineering/communication-types'
import { loadGroupFile } from './parse'
export async function findMessage(
id: MessageID,
card: CardID,
created: Date,
client: FindClient,
workspace: WorkspaceID,
filesUrl: string
): Promise<Message | undefined> {
const message = (
await client.findMessages({
card,
id,
limit: 1
})
)[0]
if (message !== undefined) {
return message
}
const group = await findGroupByDate(client, card, created)
if (group === undefined) return undefined
const parsedFile = await loadGroupFile(workspace, filesUrl, group, { retries: 5 })
return parsedFile.messages.find((it) => it.id === id)
}
async function findGroupByDate(client: FindClient, card: CardID, created: Date): Promise<MessagesGroup | undefined> {
const groups = await client.findMessagesGroups({
card,
fromDate: { lessOrEqual: created },
toDate: { greaterOrEqual: created },
limit: 1,
order: SortingOrder.Ascending,
orderBy: 'fromDate'
})
return groups[0]
}
+3
View File
@@ -13,6 +13,9 @@
"format": {
"dependsOn": ["^format"]
},
"clean": {
"dependsOn": ["^clean"]
},
"dev": {
"cache": false,
"persistent": true