diff --git a/package.json b/package.json index 034075fe14..c69f5f7f26 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "type": "module", "workspaces": ["packages/*"], "scripts": { - "build": "sh scripts/build.sh", + "build": "bun x tsc --noEmit && sh scripts/build.sh", "lint": "eslint packages/**/src/*.ts", "format": "prettier --write packages/**/src/*.ts && bun run lint" }, diff --git a/packages/client-sqlite/src/client.ts b/packages/client-sqlite/src/client.ts index 4b50d2e06c..9e4e7219fa 100644 --- a/packages/client-sqlite/src/client.ts +++ b/packages/client-sqlite/src/client.ts @@ -10,7 +10,10 @@ import { type FindNotificationContextParams, type NotificationContext, type FindNotificationsParams, - type Notification + type Notification, + type ThreadID, + type Attachment, + type Reaction } from '@communication/types' import { type Client, @@ -30,16 +33,15 @@ class DbClient implements Client { private readonly personWorkspace: string ) {} - async createMessage(card: CardID, content: RichText, creator: SocialID): Promise { + async createMessage(thread: ThreadID, content: RichText, creator: SocialID): Promise { const created = new Date() - const id = await this.db.createMessage(content, creator, created) - await this.db.placeMessage(id, card, this.workspace) + const id = await this.db.createMessage(this.workspace, thread, content, creator, created) const event: MessageCreatedEvent = { type: EventType.MessageCreated, - card, message: { id, + thread, content, creator, created, @@ -54,42 +56,42 @@ class DbClient implements Client { return id } - async removeMessage(message: MessageID) { + async removeMessage(thread: ThreadID, message: MessageID) { await this.db.removeMessage(message) - this.onEvent({ type: EventType.MessageRemoved, message }) + this.onEvent({ type: EventType.MessageRemoved, message, thread }) } - async createPatch(message: MessageID, content: RichText, creator: SocialID): Promise { + async createPatch(thread: ThreadID, message: MessageID, content: RichText, creator: SocialID): Promise { const created = new Date() await this.db.createPatch(message, content, creator, created) - this.onEvent({ type: EventType.PatchCreated, patch: { message, content, creator, created } }) + this.onEvent({ type: EventType.PatchCreated, thread, patch: { message, content, creator, created } }) } - async createReaction(message: MessageID, reaction: string, creator: SocialID): Promise { + async createReaction(thread: ThreadID, message: MessageID, reaction: string, creator: SocialID): Promise { const created = new Date() await this.db.createReaction(message, reaction, creator, created) - this.onEvent({ type: EventType.ReactionCreated, reaction: { message, reaction, creator, created } }) + this.onEvent({ type: EventType.ReactionCreated, thread, reaction: { message, reaction, creator, created } }) } - async removeReaction(message: MessageID, reaction: string, creator: SocialID): Promise { + async removeReaction(thread: ThreadID, message: MessageID, reaction: string, creator: SocialID): Promise { await this.db.removeReaction(message, reaction, creator) - this.onEvent({ type: EventType.ReactionRemoved, message, reaction, creator }) + this.onEvent({ type: EventType.ReactionRemoved, thread, message, reaction, creator }) } - async createAttachment(message: MessageID, card: CardID, creator: SocialID): Promise { + async createAttachment(thread: ThreadID, message: MessageID, card: CardID, creator: SocialID): Promise { const created = new Date() await this.db.createAttachment(message, card, creator, created) - this.onEvent({ type: EventType.AttachmentCreated, attachment: { message, card, creator, created } }) + this.onEvent({ type: EventType.AttachmentCreated, thread, attachment: { message, card, creator, created } }) } - async removeAttachment(message: MessageID, card: CardID): Promise { + async removeAttachment(thread: ThreadID, message: MessageID, card: CardID): Promise { await this.db.removeAttachment(message, card) - this.onEvent({ type: EventType.AttachmentRemoved, message, card }) + this.onEvent({ type: EventType.AttachmentRemoved, message, card, thread }) } async findMessages(params: FindMessagesParams): Promise { const rawMessages = await this.db.findMessages(this.workspace, params) - return rawMessages.map(this.toMessage) + return rawMessages.map((it) => this.toMessage(it)) } async findMessage(params: FindMessagesParams): Promise { @@ -99,15 +101,33 @@ class DbClient implements Client { toMessage(raw: any): Message { return { id: raw.id, + thread: raw.thread, content: raw.content, creator: raw.creator, created: new Date(raw.created), edited: new Date(raw.edited), - reactions: raw.reactions, - attachments: raw.attachments + reactions: raw.reactions.map((it: any) => this.toReaction(it)), + attachments: raw.attachments.map((it: any) => this.toAttachment(it)) } } + toAttachment(raw: any): Attachment { + return { + message: raw.message, + card: raw.card, + creator: raw.creator, + created: new Date(raw.created) + } + } + + toReaction(raw: any): Reaction { + return { + message: raw.message, + reaction: raw.reaction, + creator: raw.creator, + created: new Date(raw.created) + } + } async createNotification(message: MessageID, context: ContextID): Promise { await this.db.createNotification(message, context) } @@ -117,7 +137,7 @@ class DbClient implements Client { } async createNotificationContext(card: CardID, lastView?: Date, lastUpdate?: Date): Promise { - return await this.db.createContext(this.workspace, card, this.personWorkspace, lastView, lastUpdate) + return await this.db.createContext(this.personWorkspace, this.workspace, card, lastView, lastUpdate) } async updateNotificationContext(context: ContextID, update: NotificationContextUpdate): Promise { diff --git a/packages/client-ws/src/client.ts b/packages/client-ws/src/client.ts index b8329f7755..15eef05da6 100644 --- a/packages/client-ws/src/client.ts +++ b/packages/client-ws/src/client.ts @@ -1,4 +1,5 @@ import { + type Attachment, type CardID, type ContextID, type FindMessagesParams, @@ -9,8 +10,10 @@ import { type Notification, type NotificationContext, type NotificationContextUpdate, + type Reaction, type RichText, - type SocialID + type SocialID, + type ThreadID } from '@communication/types' import { type BroadcastEvent, @@ -53,10 +56,10 @@ class WsClient implements Client { } } - async createMessage(card: CardID, content: RichText, creator: SocialID): Promise { + async createMessage(thread: ThreadID, content: RichText, creator: SocialID): Promise { const event: CreateMessageEvent = { type: EventType.CreateMessage, - card, + thread, content, creator } @@ -64,17 +67,19 @@ class WsClient implements Client { return (result as CreateMessageResult).id } - async removeMessage(message: MessageID) { + async removeMessage(thread: ThreadID, message: MessageID): Promise { const event: RemoveMessageEvent = { type: EventType.RemoveMessage, + thread, message } await this.sendEvent(event) } - async createPatch(message: MessageID, content: RichText, creator: SocialID): Promise { + async createPatch(thread: ThreadID, message: MessageID, content: RichText, creator: SocialID): Promise { const event: CreatePatchEvent = { type: EventType.CreatePatch, + thread, message, content, creator @@ -82,9 +87,10 @@ class WsClient implements Client { await this.sendEvent(event) } - async createReaction(message: MessageID, reaction: string, creator: SocialID): Promise { + async createReaction(thread: ThreadID, message: MessageID, reaction: string, creator: SocialID): Promise { const event: CreateReactionEvent = { type: EventType.CreateReaction, + thread, message, reaction, creator @@ -92,9 +98,10 @@ class WsClient implements Client { await this.sendEvent(event) } - async removeReaction(message: MessageID, reaction: string, creator: SocialID): Promise { + async removeReaction(thread: ThreadID, message: MessageID, reaction: string, creator: SocialID): Promise { const event: RemoveReactionEvent = { type: EventType.RemoveReaction, + thread, message, reaction, creator @@ -102,9 +109,10 @@ class WsClient implements Client { await this.sendEvent(event) } - async createAttachment(message: MessageID, card: CardID, creator: SocialID): Promise { + async createAttachment(thread: ThreadID, message: MessageID, card: CardID, creator: SocialID): Promise { const event: CreateAttachmentEvent = { type: EventType.CreateAttachment, + thread, message, card, creator @@ -112,9 +120,10 @@ class WsClient implements Client { await this.sendEvent(event) } - async removeAttachment(message: MessageID, card: CardID): Promise { + async removeAttachment(thread: ThreadID, message: MessageID, card: CardID): Promise { const event: RemoveAttachmentEvent = { type: EventType.RemoveAttachment, + thread, message, card } @@ -123,18 +132,37 @@ class WsClient implements Client { async findMessages(params: FindMessagesParams, queryId?: number): Promise { const rawMessages = await this.ws.send('findMessages', [params, queryId]) - return rawMessages.map(this.toMessage) + return rawMessages.map((it: any) => this.toMessage(it)) } toMessage(raw: any): Message { return { id: raw.id, + thread: raw.thread, content: raw.content, creator: raw.creator, created: new Date(raw.created), edited: new Date(raw.edited), - reactions: raw.reactions, - attachments: raw.attachments + reactions: raw.reactions.map((it: any) => this.toReaction(it)), + attachments: raw.attachments.map((it: any) => this.toAttachment(it)) + } + } + + toAttachment(raw: any): Attachment { + return { + message: raw.message, + card: raw.card, + creator: raw.creator, + created: new Date(raw.created) + } + } + + toReaction(raw: any): Reaction { + return { + message: raw.message, + reaction: raw.reaction, + creator: raw.creator, + created: new Date(raw.created) } } diff --git a/packages/cockroach/migrations/01_message.sql b/packages/cockroach/migrations/01_message.sql index 1236df5ae2..e03be3b343 100644 --- a/packages/cockroach/migrations/01_message.sql +++ b/packages/cockroach/migrations/01_message.sql @@ -1,24 +1,13 @@ CREATE TABLE IF NOT EXISTS message ( - id UUID NOT NULL DEFAULT gen_random_uuid(), - content TEXT NOT NULL, - creator VARCHAR(255) NOT NULL, - created TIMESTAMPTZ NOT NULL, + id UUID NOT NULL DEFAULT gen_random_uuid(), + workspace_id UUID NOT NULL, + thread_id UUID NOT NULL, + + content TEXT NOT NULL, + creator VARCHAR(255) NOT NULL, + created TIMESTAMPTZ NOT NULL, PRIMARY KEY (id) ); -CREATE TABLE IF NOT EXISTS message_place -( - workspace_id UUID NOT NULL, - card_id UUID NOT NULL, - message_id UUID NOT NULL, - - PRIMARY KEY (workspace_id, card_id, message_id), - FOREIGN KEY (message_id) REFERENCES message (id) ON DELETE CASCADE -); - - - -CREATE INDEX idx_message_place_workspace_card ON message_place (workspace_id, card_id); -CREATE INDEX idx_message_place_message_id ON message_place (message_id); diff --git a/packages/cockroach/src/adapter.ts b/packages/cockroach/src/adapter.ts index 22185295da..a1841b6252 100644 --- a/packages/cockroach/src/adapter.ts +++ b/packages/cockroach/src/adapter.ts @@ -11,7 +11,8 @@ import { type FindNotificationContextParams, type NotificationContext, type FindNotificationsParams, - type Notification + type Notification, + type ThreadID } from '@communication/types' import type { DbAdapter } from '@communication/sdk-types' @@ -31,12 +32,14 @@ export class CockroachAdapter implements DbAdapter { this.notification = new NotificationsDb(this.sqlClient) } - async createMessage(content: RichText, creator: SocialID, created: Date): Promise { - return await this.message.createMessage(content, creator, created) - } - - async placeMessage(message: MessageID, card: CardID, workspace: string): Promise { - return await this.message.placeMessage(message, card, workspace) + async createMessage( + workspace: string, + thread: ThreadID, + content: RichText, + creator: SocialID, + created: Date + ): Promise { + return await this.message.createMessage(workspace, thread, content, creator, created) } async createPatch(message: MessageID, content: RichText, creator: SocialID, created: Date): Promise { diff --git a/packages/cockroach/src/db/message.ts b/packages/cockroach/src/db/message.ts index 2722f68bef..deedf63ac4 100644 --- a/packages/cockroach/src/db/message.ts +++ b/packages/cockroach/src/db/message.ts @@ -6,14 +6,13 @@ import { SortOrder, type SocialID, type RichText, - Direction, type Reaction, type Attachment + Direction, type Reaction, type Attachment, type ThreadID } from '@communication/types' import {BaseDb} from './base.ts' import { TableName, type MessageDb, - type MessagePlaceDb, type AttachmentDb, type ReactionDb, type PatchDb @@ -21,8 +20,10 @@ import { export class MessagesDb extends BaseDb { //Message - async createMessage(content: RichText, creator: SocialID, created: Date): Promise { + async createMessage(workspace: string, thread: ThreadID, content: RichText, creator: SocialID, created: Date): Promise { const dbData: MessageDb = { + workspace_id: workspace, + thread_id: thread, content: content, creator: creator, created: created, @@ -37,15 +38,6 @@ export class MessagesDb extends BaseDb { await this.remove(TableName.Message, {id: message}) } - async placeMessage(message: MessageID, card: CardID, workspace: string): Promise { - const dbData: MessagePlaceDb = { - workspace_id: workspace, - card_id: card, - message_id: message - } - await this.insert(TableName.MessagePlace, dbData) - } - async createPatch(message: MessageID, content: RichText, creator: SocialID, created: Date): Promise { const dbData: PatchDb = { message_id: message, @@ -104,8 +96,7 @@ export class MessagesDb extends BaseDb { ${this.subSelectPatches()}, ${this.subSelectAttachments()}, ${this.subSelectReactions()} - FROM ${TableName.Message} m - INNER JOIN ${TableName.MessagePlace} mp ON m.id = mp.message_id` + FROM ${TableName.Message} m` const {where, values} = this.buildMessageWhere(workspace, params) const orderBy = params.sort ? `ORDER BY m.created ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}` : '' @@ -118,40 +109,37 @@ export class MessagesDb extends BaseDb { } buildMessageWhere(workspace: string, params: FindMessagesParams): { where: string, values: any[] } { - const where: string[] = ['mp.workspace_id = $1'] + const where: string[] = ['m.workspace_id = $1'] const values: any[] = [workspace] - let index = 2 - for (const key of Object.keys(params)) { - const value = (params as any)[key] - switch (key) { - case 'id': { - where.push(`m.id = $${index++}`) - values.push(value) - break - } - case 'card': { - where.push(`mp.card_id = $${index++}`) - values.push(value) - break - } - case 'from': { - const exclude = params.excluded ?? false - const direction = params.direction ?? Direction.Forward - const getOperator = () => { - if (exclude) { - return direction === Direction.Forward ? '>' : '<' - } else { - return direction === Direction.Forward ? '>=' : '<=' - } - } - where.push(`m.created ${getOperator()} $${index++}`) - values.push(value) - break + let index = 2 + + if (params.id != null) { + where.push(`m.id = $${index++}`) + values.push(params.id) + } + + if (params.thread != null) { + where.push(`m.thread_id = $${index++}`) + values.push(params.thread) + } + + if (params.from != null) { + const exclude = params.excluded ?? false + const direction = params.direction ?? Direction.Forward + const getOperator = () => { + if (exclude) { + return direction === Direction.Forward ? '>' : '<' + } else { + return direction === Direction.Forward ? '>=' : '<=' } } + + where.push(`m.created ${getOperator()} $${index++}`) + values.push(params.from) } + return {where: `WHERE ${where.join(' AND ')}`, values} } @@ -198,6 +186,7 @@ export class MessagesDb extends BaseDb { return { id: row.id, + thread: row.thread_id, content: lastPatch?.content ?? row.content, creator: row.creator, created: new Date(row.created), diff --git a/packages/cockroach/src/db/notification.ts b/packages/cockroach/src/db/notification.ts index dfa9528d12..a2342041d5 100644 --- a/packages/cockroach/src/db/notification.ts +++ b/packages/cockroach/src/db/notification.ts @@ -66,12 +66,12 @@ export class NotificationsDb extends BaseDb { const sql = `UPDATE ${TableName.NotificationContext} SET ${keys.map((k, idx) => `"${k}" = $${idx + 1}`).join(', ')} - WHERE id =$${keys.length + 1}` + WHERE id = $${keys.length + 1}` await this.client.unsafe(sql, [values, context]) } - async findContexts( params: FindNotificationContextParams, personWorkspaces: string[], workspace?: string,): Promise { + async findContexts(params: FindNotificationContextParams, personWorkspaces: string[], workspace?: string,): Promise { const select = ` SELECT nc.id, nc.card_id, nc.archived_from, nc.last_view, nc.last_update FROM ${TableName.NotificationContext} nc`; @@ -91,6 +91,7 @@ export class NotificationsDb extends BaseDb { const select = ` SELECT n.message_id, n.context, + m.thread_id AS message_thread, m.content AS message_content, m.creator AS message_creator, m.created AS message_created, @@ -130,25 +131,19 @@ export class NotificationsDb extends BaseDb { const values: any[] = [] let index = 1 - if(workspace != null) { + if (workspace != null) { where.push(`nc.workspace_id = $${index++}`) values.push(workspace) } - if(personWorkspaces.length > 0) { + if (personWorkspaces.length > 0) { where.push(`nc.person_workspace IN (${personWorkspaces.map((it) => `$${index++}`).join(', ')})`) values.push(...personWorkspaces) } - for (const key of Object.keys(params)) { - const value = (params as any)[key] - switch (key) { - case 'card': { - where.push(`nc.card_id = $${index++}`) - values.push(value) - break - } - } + if (params.card != null) { + where.push(`nc.card_id = $${index++}`) + values.push(params.card) } return {where: `WHERE ${where.join(' AND ')}`, values} @@ -162,43 +157,34 @@ export class NotificationsDb extends BaseDb { const values: any[] = [personWorkspace] let index = 2 - if(workspace != null) { + if (workspace != null) { where.push(`nc.workspace_id = $${index++}`) values.push(workspace) } - for (const key of Object.keys(params)) { - const value = (params as any)[key] - switch (key) { - case 'context': { - where.push(`n.context = $${index++}`) - values.push(value) - break - } - case 'card': { - where.push(`nc.card_id = $${index++}`) - values.push(value) - break - } - case 'read': { - if (value === true) { - where.push(`nc.last_view IS NOT NULL AND nc.last_view >= m.created`) - } else if (value === false) { - where.push(`(nc.last_view IS NULL OR nc.last_view > m.created)`) - } - break - } - case 'archived': { - if (value === true) { - where.push(`nc.archived_from IS NOT NULL AND nc.archived_from >= m.created`) - } else if (value === false) { - where.push(`(nc.archived_from IS NULL OR nc.archived_from > m.created)`) - } - break - } - } + if (params.context != null) { + where.push(`n.context = $${index++}`) + values.push(params.context) } + + if (params.read === true) { + where.push(`nc.last_view IS NOT NULL AND nc.last_view >= m.created`) + } + + if (params.read === false) { + where.push(`(nc.last_view IS NULL OR nc.last_view > m.created)`) + } + + if (params.archived === true) { + where.push(`nc.archived_from IS NOT NULL AND nc.archived_from >= m.created`) + } + + if (params.archived === false) { + where.push(`(nc.archived_from IS NULL OR nc.archived_from > m.created)`) + } + + return {where: `WHERE ${where.join(' AND ')}`, values} } @@ -223,6 +209,7 @@ export class NotificationsDb extends BaseDb { return { message: { id: row.id, + thread: row.message_thread, content: lastPatch?.content ?? row.message_content, creator: row.message_creator, created, diff --git a/packages/cockroach/src/db/types.ts b/packages/cockroach/src/db/types.ts index 9dab08561a..3c7bc18372 100644 --- a/packages/cockroach/src/db/types.ts +++ b/packages/cockroach/src/db/types.ts @@ -1,9 +1,8 @@ -import type {CardID, ContextID, MessageID, RichText, SocialID } from "@communication/types" +import type {CardID, ContextID, MessageID, RichText, SocialID, ThreadID } from "@communication/types" export enum TableName { Message = 'message', Patch = 'patch', - MessagePlace = 'message_place', Attachment = 'attachment', Reaction = 'reaction', Notification = 'notification', @@ -11,6 +10,8 @@ export enum TableName { } export interface MessageDb { + workspace_id: string, + thread_id: ThreadID, content: RichText, creator: SocialID, created: Date, @@ -22,13 +23,6 @@ export interface PatchDb { creator: SocialID, created: Date, } - -export interface MessagePlaceDb { - workspace_id: string, - card_id: CardID, - message_id: MessageID -} - export interface ReactionDb { message_id: MessageID, reaction: string, diff --git a/packages/examples/src/index.ts b/packages/examples/src/index.ts index 1f03154982..27c7257439 100644 --- a/packages/examples/src/index.ts +++ b/packages/examples/src/index.ts @@ -1,12 +1,12 @@ -import { type CardID, type Message, SortOrder, type Window } from '@communication/types' +import { type Message, type SocialID, SortOrder, type ThreadID, type Window } from '@communication/types' import { getWebsocketClient } from '@communication/client-ws' import { getSqliteClient } from '@communication/client-sqlite' import { createMessagesQuery, initLiveQueries } from '@communication/client-query' -const card1: CardID = 'cd0aba36-1c4f-4170-95f2-27a12a5415f6' +const thread = 'cd0aba36-1c4f-4170-95f2-27a12a5415f6' as ThreadID const workspace = 'cd0aba36-1c4f-4170-95f2-27a12a5415f6' const personWorkspace = 'cd0aba36-1c4f-4170-95f2-27a12a5415f5' -const creator1 = 'email:vasya@huly.com' +const creator1 = 'email:vasya@huly.com' as SocialID async function getClient(type: 'ws' | 'sqlite') { if (type === 'ws') { @@ -26,7 +26,7 @@ export async function example() { let window: Window | undefined = undefined - query1.query({ card: card1, sort: SortOrder.Desc }, (res) => { + query1.query({ thread, sort: SortOrder.Desc }, (res) => { window = res const r = window.getResult() r.reverse() @@ -44,19 +44,19 @@ export async function example() { }) async function editMessage(message: Message) { - await client.createPatch(message.id, message.content + '_1_', creator1) + await client.createPatch(thread, message.id, message.content + '_1_', creator1) } async function deleteMessage(message: Message) { - await client.removeMessage(message.id) + await client.removeMessage(thread, message.id) } async function addReaction(message: Message) { - await client.createReaction(message.id, '👍', creator1) + await client.createReaction(thread, message.id, '👍', creator1) } async function removeReaction(message: Message) { - await client.removeReaction(message.id, '👍', creator1) + await client.removeReaction(thread, message.id, '👍', creator1) } function scrollToBottom() { @@ -116,7 +116,7 @@ export async function example() { const el = event.target?.getElementsByTagName('input')[0] as HTMLInputElement if (el.value == '' || el.value == null) return - await client.createMessage(card1, el.value, creator1) + await client.createMessage(thread, el.value, creator1) el.value = '' }) diff --git a/packages/query/src/messages/query.ts b/packages/query/src/messages/query.ts index 794b346c26..a466d46701 100644 --- a/packages/query/src/messages/query.ts +++ b/packages/query/src/messages/query.ts @@ -1,5 +1,4 @@ import { - type CardID, type FindMessagesParams, type ID, type Message, @@ -65,7 +64,7 @@ export class MessagesQuery extends BaseQuery { const exists = this.result.get(message.id) if (exists !== undefined) return - if (!this.match(message, event.card)) return + if (!this.match(message)) return if (this.result.isTail()) { if (this.params.sort === SortOrder.Asc) { @@ -77,11 +76,11 @@ export class MessagesQuery extends BaseQuery { } } - private match(message: Message, card: CardID): boolean { + private match(message: Message): boolean { if (this.params.id != null && this.params.id !== message.id) { return false } - if (this.params.card != null && this.params.card !== card) { + if (this.params.thread != null && this.params.thread !== message.thread) { return false } return true diff --git a/packages/sdk-types/src/client.ts b/packages/sdk-types/src/client.ts index af3edce94b..d232cb73f8 100644 --- a/packages/sdk-types/src/client.ts +++ b/packages/sdk-types/src/client.ts @@ -9,22 +9,23 @@ import type { NotificationContextUpdate, RichText, SocialID, - Notification + Notification, + ThreadID } from '@communication/types' import type { FindMessagesParams } from '@communication/types' import type { BroadcastEvent } from './event.ts' export interface Client { - createMessage(card: CardID, content: RichText, creator: SocialID): Promise - removeMessage(id: MessageID): Promise - createPatch(message: MessageID, content: RichText, creator: SocialID): Promise + createMessage(thread: ThreadID, content: RichText, creator: SocialID): Promise + removeMessage(thread: ThreadID, id: MessageID): Promise + createPatch(thread: ThreadID, message: MessageID, content: RichText, creator: SocialID): Promise - createReaction(message: MessageID, reaction: string, creator: SocialID): Promise - removeReaction(message: MessageID, reaction: string, creator: SocialID): Promise + createReaction(thread: ThreadID, message: MessageID, reaction: string, creator: SocialID): Promise + removeReaction(thread: ThreadID, message: MessageID, reaction: string, creator: SocialID): Promise - createAttachment(message: MessageID, card: CardID, creator: SocialID): Promise - removeAttachment(message: MessageID, card: CardID): Promise + createAttachment(thread: ThreadID, message: MessageID, card: CardID, creator: SocialID): Promise + removeAttachment(thread: ThreadID, message: MessageID, card: CardID): Promise createNotification(message: MessageID, context: ContextID): Promise removeNotification(message: MessageID, context: ContextID): Promise diff --git a/packages/sdk-types/src/db.ts b/packages/sdk-types/src/db.ts index 0e5c9c38d3..1723101016 100644 --- a/packages/sdk-types/src/db.ts +++ b/packages/sdk-types/src/db.ts @@ -10,14 +10,19 @@ import type { NotificationContextUpdate, RichText, SocialID, - Notification + Notification, + ThreadID } from '@communication/types' export interface DbAdapter { - createMessage(content: RichText, creator: SocialID, created: Date): Promise + createMessage( + workspace: string, + thread: ThreadID, + content: RichText, + creator: SocialID, + created: Date + ): Promise removeMessage(id: MessageID): Promise - - placeMessage(message: MessageID, card: CardID, workspace: string): Promise createPatch(message: MessageID, content: RichText, creator: SocialID, created: Date): Promise createReaction(message: MessageID, reaction: string, creator: SocialID, created: Date): Promise diff --git a/packages/sdk-types/src/event.ts b/packages/sdk-types/src/event.ts index 00f49b6714..8771477c53 100644 --- a/packages/sdk-types/src/event.ts +++ b/packages/sdk-types/src/event.ts @@ -10,7 +10,8 @@ import type { Reaction, RichText, SocialID, - Notification + Notification, + ThreadID } from '@communication/types' export enum EventType { @@ -57,18 +58,20 @@ export type Event = export interface CreateMessageEvent { type: EventType.CreateMessage - card: CardID + thread: ThreadID content: RichText creator: SocialID } export interface RemoveMessageEvent { type: EventType.RemoveMessage + thread: ThreadID message: MessageID } export interface CreatePatchEvent { type: EventType.CreatePatch + thread: ThreadID message: MessageID content: RichText creator: SocialID @@ -76,6 +79,7 @@ export interface CreatePatchEvent { export interface CreateReactionEvent { type: EventType.CreateReaction + thread: ThreadID message: MessageID reaction: string creator: SocialID @@ -83,6 +87,7 @@ export interface CreateReactionEvent { export interface RemoveReactionEvent { type: EventType.RemoveReaction + thread: ThreadID message: MessageID reaction: string creator: SocialID @@ -90,6 +95,7 @@ export interface RemoveReactionEvent { export interface CreateAttachmentEvent { type: EventType.CreateAttachment + thread: ThreadID message: MessageID card: CardID creator: SocialID @@ -97,6 +103,7 @@ export interface CreateAttachmentEvent { export interface RemoveAttachmentEvent { type: EventType.RemoveAttachment + thread: ThreadID message: MessageID card: CardID } @@ -158,27 +165,30 @@ export type BroadcastEvent = export interface MessageCreatedEvent { type: EventType.MessageCreated - card: CardID message: Message } export interface MessageRemovedEvent { type: EventType.MessageRemoved + thread: ThreadID message: MessageID } export interface PatchCreatedEvent { type: EventType.PatchCreated + thread: ThreadID patch: Patch } export interface ReactionCreatedEvent { type: EventType.ReactionCreated + thread: ThreadID reaction: Reaction } export interface ReactionRemovedEvent { type: EventType.ReactionRemoved + thread: ThreadID message: MessageID reaction: string creator: SocialID @@ -186,11 +196,13 @@ export interface ReactionRemovedEvent { export interface AttachmentCreatedEvent { type: EventType.AttachmentCreated + thread: ThreadID attachment: Attachment } export interface AttachmentRemovedEvent { type: EventType.AttachmentRemoved + thread: ThreadID message: MessageID card: CardID } diff --git a/packages/server/src/eventProcessor.ts b/packages/server/src/eventProcessor.ts index ecdb2d9a5d..cffcbdca96 100644 --- a/packages/server/src/eventProcessor.ts +++ b/packages/server/src/eventProcessor.ts @@ -73,10 +73,10 @@ export class EventProcessor { private async createMessage(event: CreateMessageEvent): Promise { const created = new Date() - const id = await this.db.createMessage(event.content, event.creator, created) - await this.db.placeMessage(id, event.card, this.workspace) + const id = await this.db.createMessage(this.workspace, event.thread, event.content, event.creator, created) const message: Message = { id, + thread: event.thread, content: event.content, creator: event.creator, created: created, @@ -86,7 +86,6 @@ export class EventProcessor { } const broadcastEvent: MessageCreatedEvent = { type: EventType.MessageCreated, - card: event.card, message } return { @@ -107,6 +106,7 @@ export class EventProcessor { } const broadcastEvent: PatchCreatedEvent = { type: EventType.PatchCreated, + thread: event.thread, patch } return { @@ -120,6 +120,7 @@ export class EventProcessor { const broadcastEvent: MessageRemovedEvent = { type: EventType.MessageRemoved, + thread: event.thread, message: event.message } @@ -141,6 +142,7 @@ export class EventProcessor { } const broadcastEvent: ReactionCreatedEvent = { type: EventType.ReactionCreated, + thread: event.thread, reaction } return { @@ -153,6 +155,7 @@ export class EventProcessor { await this.db.removeReaction(event.message, event.reaction, event.creator) const broadcastEvent: ReactionRemovedEvent = { type: EventType.ReactionRemoved, + thread: event.thread, message: event.message, reaction: event.reaction, creator: event.creator @@ -175,6 +178,7 @@ export class EventProcessor { } const broadcastEvent: AttachmentCreatedEvent = { type: EventType.AttachmentCreated, + thread: event.thread, attachment } @@ -188,6 +192,7 @@ export class EventProcessor { await this.db.removeAttachment(event.message, event.card) const broadcastEvent: AttachmentRemovedEvent = { type: EventType.AttachmentRemoved, + thread: event.thread, message: event.message, card: event.card } @@ -222,9 +227,9 @@ export class EventProcessor { private async createNotificationContext(event: CreateNotificationContextEvent): Promise { const id = await this.db.createContext( + this.personWorkspace, this.workspace, event.card, - this.personWorkspace, event.lastView, event.lastUpdate ) diff --git a/packages/server/src/main.ts b/packages/server/src/main.ts index 8d22d60e43..3fa14adf07 100644 --- a/packages/server/src/main.ts +++ b/packages/server/src/main.ts @@ -2,6 +2,7 @@ import WebSocket, { WebSocketServer, type RawData } from 'ws' import { createDbAdapter } from '@communication/cockroach' import type { Response, HelloRequest } from '@communication/sdk-types' import { decodeToken } from '@hcengineering/server-token' +import type { SocialID } from '@communication/types' import type { ConnectionInfo } from './types.ts' import { deserializeRequest, serializeResponse } from './utils/serialize.ts' @@ -137,5 +138,5 @@ async function validateToken(token: string): Promise { } const personWorkspace = 'cd0aba36-1c4f-4170-95f2-27a12a5415f7' - return { workspace: info.workspaceId, personWorkspace, socialId: email } + return { workspace: info.workspaceId, personWorkspace, socialId: email as SocialID } } diff --git a/packages/server/src/manager.ts b/packages/server/src/manager.ts index 3bb640cc36..6ca6a82a6c 100644 --- a/packages/server/src/manager.ts +++ b/packages/server/src/manager.ts @@ -3,12 +3,16 @@ import { type BroadcastEvent, type DbAdapter, EventType, - type MessageCreatedEvent, type NotificationContextCreatedEvent, type NotificationCreatedEvent, type Response } from '@communication/sdk-types' -import type { FindMessagesParams, FindNotificationContextParams, FindNotificationsParams } from '@communication/types' +import type { + FindMessagesParams, + FindNotificationContextParams, + FindNotificationsParams, + MessageID +} from '@communication/types' import { Session } from './session' import type { ConnectionInfo } from './types' @@ -112,14 +116,40 @@ export class Manager { private match(event: BroadcastEvent, info: SessionInfo): boolean { switch (event.type) { case EventType.MessageCreated: - return this.matchMessagesQuery(event, Array.from(info.messageQueries.values())) + return this.matchMessagesQuery( + { id: event.message.id, thread: event.message.thread }, + Array.from(info.messageQueries.values()) + ) case EventType.PatchCreated: + return this.matchMessagesQuery( + { thread: event.thread, id: event.patch.message }, + Array.from(info.messageQueries.values()) + ) case EventType.MessageRemoved: + return this.matchMessagesQuery( + { thread: event.thread, id: event.message }, + Array.from(info.messageQueries.values()) + ) case EventType.ReactionCreated: + return this.matchMessagesQuery( + { thread: event.thread, id: event.reaction.message }, + Array.from(info.messageQueries.values()) + ) case EventType.ReactionRemoved: + return this.matchMessagesQuery( + { thread: event.thread, id: event.message }, + Array.from(info.messageQueries.values()) + ) case EventType.AttachmentCreated: + return this.matchMessagesQuery( + { thread: event.thread, id: event.attachment.message }, + Array.from(info.messageQueries.values()) + ) case EventType.AttachmentRemoved: - return info.messageQueries.size > 0 + return this.matchMessagesQuery( + { thread: event.thread, id: event.message }, + Array.from(info.messageQueries.values()) + ) case EventType.NotificationCreated: return ( info.session.info.personWorkspace === event.personWorkspace && @@ -139,12 +169,12 @@ export class Manager { } } - private matchMessagesQuery(event: MessageCreatedEvent, queries: FindMessagesParams[]): boolean { + private matchMessagesQuery(params: { id?: MessageID; thread?: string }, queries: FindMessagesParams[]): boolean { if (queries.length === 0) return false for (const query of queries) { - if (query.id != null && query.id !== event.message.id) continue - if (query.card != null && query.card !== event.card) continue + if (query.id != null && query.id !== params.id) continue + if (query.thread != null && query.thread !== params.thread) continue return true } diff --git a/packages/server/src/triggers.ts b/packages/server/src/triggers.ts index 4f131853c6..4500a2ec20 100644 --- a/packages/server/src/triggers.ts +++ b/packages/server/src/triggers.ts @@ -6,7 +6,7 @@ import { type NotificationContextCreatedEvent, type NotificationCreatedEvent } from '@communication/sdk-types' -import type { NotificationContext, ContextID } from '@communication/types' +import type { NotificationContext, ContextID, CardID } from '@communication/types' export class Triggers { constructor(private readonly db: DbAdapter) {} @@ -21,7 +21,7 @@ export class Triggers { } private async createNotifications(event: MessageCreatedEvent, workspace: string): Promise { - const card = event.card + const card = event.message.thread as any as CardID const subscribedPersonWorkspaces = ['cd0aba36-1c4f-4170-95f2-27a12a5415f7', 'cd0aba36-1c4f-4170-95f2-27a12a5415f8'] const res: BroadcastEvent[] = [] @@ -62,7 +62,7 @@ export class Triggers { private async getOrCreateContextId( workspace: string, - card: string, + card: CardID, personWorkspace: string, res: BroadcastEvent[], lastUpdate: Date, @@ -71,7 +71,7 @@ export class Triggers { if (context !== undefined) { return context.id } else { - const contextId = await this.db.createContext(workspace, card, personWorkspace, undefined, lastUpdate) + const contextId = await this.db.createContext(personWorkspace, workspace, card, undefined, lastUpdate) const newContext = { id: contextId, card, diff --git a/packages/sqlite-wasm/src/adapter.ts b/packages/sqlite-wasm/src/adapter.ts index 6043bf1f4f..20e3b42ba2 100644 --- a/packages/sqlite-wasm/src/adapter.ts +++ b/packages/sqlite-wasm/src/adapter.ts @@ -10,7 +10,8 @@ import { type FindNotificationsParams, type FindNotificationContextParams, type NotificationContext, - type Notification + type Notification, + type ThreadID } from '@communication/types' import type { DbAdapter } from '@communication/sdk-types' @@ -31,12 +32,14 @@ export class SqliteAdapter implements DbAdapter { this.notification = new NotificationsDb(worker, dbId) } - async createMessage(content: RichText, creator: SocialID, created: Date): Promise { - return await this.message.createMessage(content, creator, created) - } - - async placeMessage(message: MessageID, card: CardID, workspace: string): Promise { - return await this.message.placeMessage(message, card, workspace) + async createMessage( + workspace: string, + thread: ThreadID, + content: RichText, + creator: SocialID, + created: Date + ): Promise { + return await this.message.createMessage(workspace, thread, content, creator, created) } async createPatch(message: MessageID, content: RichText, creator: SocialID, created: Date): Promise { diff --git a/packages/sqlite-wasm/src/db/message.ts b/packages/sqlite-wasm/src/db/message.ts index b6d751893e..581fc0f51b 100644 --- a/packages/sqlite-wasm/src/db/message.ts +++ b/packages/sqlite-wasm/src/db/message.ts @@ -6,14 +6,13 @@ import { SortOrder, type SocialID, type RichText, - Direction, type Reaction, type Attachment + Direction, type Reaction, type Attachment, type ThreadID } from '@communication/types' import {BaseDb} from './base.ts' import { TableName, type MessageDb, - type MessagePlaceDb, type AttachmentDb, type ReactionDb, type PatchDb @@ -21,9 +20,11 @@ import { export class MessagesDb extends BaseDb { //Message - async createMessage(content: RichText, creator: SocialID, created: Date): Promise { + async createMessage(workspace: string, thread: ThreadID, content: RichText, creator: SocialID, created: Date): Promise { const dbData: MessageDb = { id: self.crypto.randomUUID(), + workspace_id: workspace, + thread_id: thread, content: content, creator: creator, created: created, @@ -36,15 +37,6 @@ export class MessagesDb extends BaseDb { await this.remove(TableName.Message, {id: message}) } - async placeMessage(message: MessageID, card: CardID, workspace: string): Promise { - const dbData: MessagePlaceDb = { - workspace_id: workspace, - card_id: card, - message_id: message - } - await this.insert(TableName.MessagePlace, dbData) - } - async createPatch(message: MessageID, content: RichText, creator: SocialID, created: Date): Promise { const dbData: PatchDb = { id: self.crypto.randomUUID(), @@ -97,6 +89,7 @@ export class MessagesDb extends BaseDb { //Find messages async find(workspace: string, params: FindMessagesParams): Promise { const select = `SELECT m.id, + m.thread_id, m.content, m.creator, m.created, @@ -124,12 +117,11 @@ export class MessagesDb extends BaseDb { ) ) AS reactions FROM ${TableName.Message} m - INNER JOIN ${TableName.MessagePlace} mp ON m.id = mp.message_id - LEFT JOIN ${TableName.Patch} p ON p.message_id = m.id - LEFT JOIN ${TableName.Attachment} a ON a.message_id = m.id - LEFT JOIN ${TableName.Reaction} r ON r.message_id = m.id` + LEFT JOIN ${TableName.Patch} p ON p.message_id = m.id + LEFT JOIN ${TableName.Attachment} a ON a.message_id = m.id + LEFT JOIN ${TableName.Reaction} r ON r.message_id = m.id` - const where= this.buildMessageWhere(workspace, params) + const where = this.buildMessageWhere(workspace, params) const groupBy = `GROUP BY m.id` const orderBy = params.sort ? `ORDER BY m.created ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}` : '' const limit = params.limit ? ` LIMIT ${params.limit}` : '' @@ -141,34 +133,27 @@ export class MessagesDb extends BaseDb { } buildMessageWhere(workspace: string, params: FindMessagesParams): string { - const where: string[] = [`mp.workspace_id = '${workspace}'`] - for (const key of Object.keys(params)) { - const value = (params as any)[key] - switch (key) { - case 'id': { - where.push(`m.id = '${value}'`) - break - } - case 'card': { - where.push(`mp.card_id = '${value}'`) - break - } - case 'from': { - if(value == null) continue - const exclude = params.excluded ?? false - const direction = params.direction ?? Direction.Forward - const getOperator = () => { - if (exclude) { - return direction === Direction.Forward ? '>' : '<' - } else { - return direction === Direction.Forward ? '>=' : '<=' - } - } + const where: string[] = [`m.workspace_id = '${workspace}'`] - where.push(`m.created ${getOperator()} ${value}`) - break + if (params.thread != null) { + where.push(`m.thread_id = '${params.thread}'`) + } + if (params.id != null) { + where.push(`m.id = '${params.id}'`) + } + + if (params.from != null) { + const exclude = params.excluded ?? false + const direction = params.direction ?? Direction.Forward + const getOperator = () => { + if (exclude) { + return direction === Direction.Forward ? '>' : '<' + } else { + return direction === Direction.Forward ? '>=' : '<=' } } + + where.push(`m.created ${getOperator()} ${params.from}`) } return `WHERE ${where.join(' AND ')}` @@ -183,6 +168,7 @@ export class MessagesDb extends BaseDb { return { id: row.id, + thread: row.thread_id, content: lastPatch?.content ?? row.content, creator: row.creator, created: new Date(row.created), diff --git a/packages/sqlite-wasm/src/db/notification.ts b/packages/sqlite-wasm/src/db/notification.ts index a917b62e51..2cf901a866 100644 --- a/packages/sqlite-wasm/src/db/notification.ts +++ b/packages/sqlite-wasm/src/db/notification.ts @@ -9,7 +9,7 @@ import { } from '@communication/types' import {BaseDb} from './base.ts' -import {TableName, type ContextDb, type NotificationDb } from './types.ts' +import {TableName, type ContextDb, type NotificationDb} from './types.ts' export class NotificationsDb extends BaseDb { async createNotification(message: MessageID, context: ContextID): Promise { @@ -80,7 +80,13 @@ export class NotificationsDb extends BaseDb { async findContexts(params: FindNotificationContextParams, personWorkspaces: string[], workspace?: string,): Promise { const select = ` - SELECT nc.id, nc.card_id, nc.archived_from, nc.last_view, nc.last_update, nc.workspace_id, nc.person_workspace + SELECT nc.id, + nc.card_id, + nc.archived_from, + nc.last_view, + nc.last_update, + nc.workspace_id, + nc.person_workspace FROM ${TableName.NotificationContext} nc`; const where = this.buildContextWhere(params, personWorkspaces, workspace); // const orderSql = `ORDER BY nc.created ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}` @@ -96,104 +102,87 @@ export class NotificationsDb extends BaseDb { async findNotifications(params: FindNotificationsParams, personWorkspace: string, workspace?: string): Promise { //TODO: should join with attachments and reactions? const select = ` - SELECT - n.message_id, - n.context_id, - m.content AS message_content, - m.creator AS message_creator, - m.created AS message_created, - nc.card_id, - nc.archived_from, - nc.last_view, - nc.last_update, - json_group_array( - json_object( - 'id', p.id, - 'content', p.content, - 'creator', p.creator, - 'created', p.created - ) - ) AS patches - FROM - ${TableName.Notification} n - JOIN - ${TableName.NotificationContext} nc ON n.context_id = nc.id - JOIN - ${TableName.Message} m ON n.message_id = m.id - LEFT JOIN - ${TableName.Patch} p ON p.message_id = m.id + SELECT n.message_id, + n.context_id, + m.thread_id AS message_thread, + m.content AS message_content, + m.creator AS message_creator, + m.created AS message_created, + nc.card_id, + nc.archived_from, + nc.last_view, + nc.last_update, + json_group_array( + json_object( + 'id', p.id, + 'content', p.content, + 'creator', p.creator, + 'created', p.created + ) + ) AS patches + FROM ${TableName.Notification} n + JOIN + ${TableName.NotificationContext} nc ON n.context_id = nc.id + JOIN + ${TableName.Message} m ON n.message_id = m.id + LEFT JOIN + ${TableName.Patch} p ON p.message_id = m.id `; const where = this.buildNotificationWhere(params, personWorkspace, workspace) const groupBy = `GROUP BY n.message_id, n.context_id, m.id, nc.card_id, nc.archived_from, nc.last_view, nc.last_update`; const orderBy = `ORDER BY m.created ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}` const limit = params.limit ? ` LIMIT ${params.limit}` : '' - const sql = [select, where, groupBy,orderBy, limit].join(' ') + const sql = [select, where, groupBy, orderBy, limit].join(' ') const result = await this.select(sql) return result.map(it => this.toNotification(it)); } - buildContextWhere(params: FindNotificationContextParams, personWorkspaces: string[], workspace?: string, ): string { + buildContextWhere(params: FindNotificationContextParams, personWorkspaces: string[], workspace?: string,): string { const where: string[] = [] - if(workspace != null) { + if (workspace != null) { where.push(`nc.workspace_id = '${workspace}'`) } - if(personWorkspaces.length > 0) { + if (personWorkspaces.length > 0) { where.push(`nc.person_workspace IN (${personWorkspaces.map(it => `'${it}'`).join(', ')})`) } - for (const key of Object.keys(params)) { - const value = (params as any)[key] - switch (key) { - case 'card': { - where.push(`nc.card_id = '${value}'`) - break - } - } + if (params.card != null) { + where.push(`nc.card_id = '${params.card}'`) } return `WHERE ${where.join(' AND ')}` } - buildNotificationWhere(params: FindNotificationsParams, personWorkspace: string, workspace?: string ): string { + buildNotificationWhere(params: FindNotificationsParams, personWorkspace: string, workspace?: string): string { const where: string[] = [`nc.person_workspace = '${personWorkspace}'`] - if(workspace != null) { + if (workspace != null) { where.push(`nc.workspace_id = '${workspace}'`) } - for (const key of Object.keys(params)) { - const value = (params as any)[key] - switch (key) { - case 'context': { - where.push(`n.context = '${value}'`) - break - } - case 'card': { - where.push(`nc.card_id = '${value}'`) - break - } - case 'read': { - if (value === true) { - where.push(`nc.last_view IS NOT NULL AND nc.last_view >= m.created`) - } else if (value === false) { - where.push(`(nc.last_view IS NULL OR nc.last_view > m.created)`) - } - break - } - case 'archived': { - if (value === true) { - where.push(`nc.archived_from IS NOT NULL AND nc.archived_from >= m.created`) - } else if (value === false) { - where.push(`(nc.archived_from IS NULL OR nc.archived_from > m.created)`) - } - break - } - } + if (params.context != null) { + where.push(`n.context_id = '${params.context}'`) } - return `WHERE ${where.join(' AND ')}` + if (params.read === true) { + where.push(`nc.last_view IS NOT NULL AND nc.last_view >= m.created`) + } + + if (params.read === false) { + where.push(`(nc.last_view IS NULL OR nc.last_view > m.created)`) + } + + if (params.archived === true) { + where.push(`nc.archived_from IS NOT NULL AND nc.archived_from >= m.created`) + } + + if (params.archived === false) { + where.push(`(nc.archived_from IS NULL OR nc.archived_from > m.created)`) + } + + return `WHERE ${where.join(' AND ')}` } toNotificationContext(row: any): NotificationContext { @@ -217,6 +206,7 @@ export class NotificationsDb extends BaseDb { return { message: { id: row.message_id, + thread: row.message_thread, content: lastPatch?.content ?? row.message_content, creator: row.message_creator, created, diff --git a/packages/sqlite-wasm/src/db/types.ts b/packages/sqlite-wasm/src/db/types.ts index ac6f7940fc..4c7285fc46 100644 --- a/packages/sqlite-wasm/src/db/types.ts +++ b/packages/sqlite-wasm/src/db/types.ts @@ -3,7 +3,6 @@ import type {CardID, ContextID, MessageID, RichText, SocialID } from "@communica export enum TableName { Message = 'message', Patch = 'patch', - MessagePlace = 'message_place', Attachment = 'attachment', Reaction = 'reaction', Notification = 'notification', @@ -12,6 +11,8 @@ export enum TableName { export interface MessageDb { id: string + workspace_id: string, + thread_id: string, content: RichText, creator: SocialID, created: Date, @@ -25,12 +26,6 @@ export interface PatchDb { created: Date, } -export interface MessagePlaceDb { - workspace_id: string, - card_id: CardID, - message_id: MessageID -} - export interface ReactionDb { message_id: MessageID, reaction: string, diff --git a/packages/sqlite-wasm/src/migrations.ts b/packages/sqlite-wasm/src/migrations.ts index 3a0259c6c2..1a59020ab3 100644 --- a/packages/sqlite-wasm/src/migrations.ts +++ b/packages/sqlite-wasm/src/migrations.ts @@ -10,40 +10,17 @@ async function migrationV1(worker: Sqlite3Worker1Promiser, dbId: string): Promis sql: ` CREATE TABLE IF NOT EXISTS message ( - id TEXT NOT NULL, - content TEXT NOT NULL, - creator TEXT NOT NULL, - created DATETIME NOT NULL, + id TEXT NOT NULL, + workspace_id TEXT NOT NULL, + thread_id TEXT NOT NULL, + content TEXT NOT NULL, + creator TEXT NOT NULL, + created DATETIME NOT NULL, PRIMARY KEY (id) ) ` }) - await worker('exec', { - dbId, - sql: ` - CREATE TABLE IF NOT EXISTS message_place - ( - workspace_id TEXT NOT NULL, - card_id TEXT NOT NULL, - message_id TEXT NOT NULL, - - PRIMARY KEY (workspace_id, card_id, message_id), - FOREIGN KEY (message_id) REFERENCES message (id) ON DELETE CASCADE - ) - ` - }) - - await worker('exec', { - dbId, - sql: `CREATE INDEX IF NOT EXISTS idx_message_place_workspace_card ON message_place (workspace_id, card_id)` - }) - - await worker('exec', { - dbId, - sql: `CREATE INDEX IF NOT EXISTS idx_message_place_message_id ON message_place (message_id)` - }) - await worker('exec', { dbId, sql: ` @@ -110,29 +87,29 @@ async function migrationV1(worker: Sqlite3Worker1Promiser, dbId: string): Promis await worker('exec', { dbId, sql: ` - CREATE TABLE IF NOT EXISTS notification_context - ( - id TEXT NOT NULL, - workspace_id TEXT NOT NULL, - card_id TEXT NOT NULL, - person_workspace TEXT NOT NULL, - archived_from DATETIME, - last_view DATETIME, - last_update DATETIME, + CREATE TABLE IF NOT EXISTS notification_context + ( + id TEXT NOT NULL, + workspace_id TEXT NOT NULL, + card_id TEXT NOT NULL, + person_workspace TEXT NOT NULL, + archived_from DATETIME, + last_view DATETIME, + last_update DATETIME, - PRIMARY KEY (id), - UNIQUE (workspace_id, card_id, person_workspace) - ); + PRIMARY KEY (id), + UNIQUE (workspace_id, card_id, person_workspace) + ); - CREATE TABLE IF NOT EXISTS notification - ( - message_id TEXT NOT NULL, - context_id TEXT NOT NULL, + CREATE TABLE IF NOT EXISTS notification + ( + message_id TEXT NOT NULL, + context_id TEXT NOT NULL, - PRIMARY KEY (message_id, context_id), - FOREIGN KEY (message_id) REFERENCES message (id) ON DELETE CASCADE, - FOREIGN KEY (context_id) REFERENCES notification_context (id) ON DELETE CASCADE - ); - ` + PRIMARY KEY (message_id, context_id), + FOREIGN KEY (message_id) REFERENCES message (id) ON DELETE CASCADE, + FOREIGN KEY (context_id) REFERENCES notification_context (id) ON DELETE CASCADE + ); + ` }) } diff --git a/packages/types/src/message.ts b/packages/types/src/message.ts index 5641439e2e..df3c18bd42 100644 --- a/packages/types/src/message.ts +++ b/packages/types/src/message.ts @@ -1,5 +1,6 @@ -export type CardID = string -export type SocialID = string +export type CardID = string & { card: true } +export type SocialID = string & { social: true } +export type ThreadID = string & { thread: true } export type RichText = string export type ID = string @@ -12,6 +13,7 @@ interface Object { export interface Message extends Object { id: MessageID + thread: ThreadID content: RichText edited: Date reactions: Reaction[] diff --git a/packages/types/src/query.ts b/packages/types/src/query.ts index 302893542a..7109e761a1 100644 --- a/packages/types/src/query.ts +++ b/packages/types/src/query.ts @@ -1,4 +1,4 @@ -import type { CardID, MessageID } from './message' +import type { CardID, MessageID, ThreadID } from './message' import type { ContextID } from './notification' export enum SortOrder { @@ -31,7 +31,7 @@ interface FindParams { export interface FindMessagesParams extends FindParams { id?: MessageID - card?: CardID + thread?: ThreadID } export interface FindNotificationsParams extends FindParams {