mirror of
https://github.com/hcengineering/platform.git
synced 2026-10-01 05:55:09 +02:00
Remove message place table (#11)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
+1
-1
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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<MessageID> {
|
||||
async createMessage(thread: ThreadID, content: RichText, creator: SocialID): Promise<MessageID> {
|
||||
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<void> {
|
||||
async createPatch(thread: ThreadID, message: MessageID, content: RichText, creator: SocialID): Promise<void> {
|
||||
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<void> {
|
||||
async createReaction(thread: ThreadID, message: MessageID, reaction: string, creator: SocialID): Promise<void> {
|
||||
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<void> {
|
||||
async removeReaction(thread: ThreadID, message: MessageID, reaction: string, creator: SocialID): Promise<void> {
|
||||
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<void> {
|
||||
async createAttachment(thread: ThreadID, message: MessageID, card: CardID, creator: SocialID): Promise<void> {
|
||||
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<void> {
|
||||
async removeAttachment(thread: ThreadID, message: MessageID, card: CardID): Promise<void> {
|
||||
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<Message[]> {
|
||||
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<Message | undefined> {
|
||||
@@ -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<void> {
|
||||
await this.db.createNotification(message, context)
|
||||
}
|
||||
@@ -117,7 +137,7 @@ class DbClient implements Client {
|
||||
}
|
||||
|
||||
async createNotificationContext(card: CardID, lastView?: Date, lastUpdate?: Date): Promise<ContextID> {
|
||||
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<void> {
|
||||
|
||||
@@ -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<MessageID> {
|
||||
async createMessage(thread: ThreadID, content: RichText, creator: SocialID): Promise<MessageID> {
|
||||
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<void> {
|
||||
const event: RemoveMessageEvent = {
|
||||
type: EventType.RemoveMessage,
|
||||
thread,
|
||||
message
|
||||
}
|
||||
await this.sendEvent(event)
|
||||
}
|
||||
|
||||
async createPatch(message: MessageID, content: RichText, creator: SocialID): Promise<void> {
|
||||
async createPatch(thread: ThreadID, message: MessageID, content: RichText, creator: SocialID): Promise<void> {
|
||||
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<void> {
|
||||
async createReaction(thread: ThreadID, message: MessageID, reaction: string, creator: SocialID): Promise<void> {
|
||||
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<void> {
|
||||
async removeReaction(thread: ThreadID, message: MessageID, reaction: string, creator: SocialID): Promise<void> {
|
||||
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<void> {
|
||||
async createAttachment(thread: ThreadID, message: MessageID, card: CardID, creator: SocialID): Promise<void> {
|
||||
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<void> {
|
||||
async removeAttachment(thread: ThreadID, message: MessageID, card: CardID): Promise<void> {
|
||||
const event: RemoveAttachmentEvent = {
|
||||
type: EventType.RemoveAttachment,
|
||||
thread,
|
||||
message,
|
||||
card
|
||||
}
|
||||
@@ -123,18 +132,37 @@ class WsClient implements Client {
|
||||
|
||||
async findMessages(params: FindMessagesParams, queryId?: number): Promise<Message[]> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<MessageID> {
|
||||
return await this.message.createMessage(content, creator, created)
|
||||
}
|
||||
|
||||
async placeMessage(message: MessageID, card: CardID, workspace: string): Promise<void> {
|
||||
return await this.message.placeMessage(message, card, workspace)
|
||||
async createMessage(
|
||||
workspace: string,
|
||||
thread: ThreadID,
|
||||
content: RichText,
|
||||
creator: SocialID,
|
||||
created: Date
|
||||
): Promise<MessageID> {
|
||||
return await this.message.createMessage(workspace, thread, content, creator, created)
|
||||
}
|
||||
|
||||
async createPatch(message: MessageID, content: RichText, creator: SocialID, created: Date): Promise<void> {
|
||||
|
||||
@@ -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<MessageID> {
|
||||
async createMessage(workspace: string, thread: ThreadID, content: RichText, creator: SocialID, created: Date): Promise<MessageID> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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),
|
||||
|
||||
@@ -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<NotificationContext[]> {
|
||||
async findContexts(params: FindNotificationContextParams, personWorkspaces: string[], workspace?: string,): Promise<NotificationContext[]> {
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<Message> | 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 = ''
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
type CardID,
|
||||
type FindMessagesParams,
|
||||
type ID,
|
||||
type Message,
|
||||
@@ -65,7 +64,7 @@ export class MessagesQuery extends BaseQuery<Message, FindMessagesParams> {
|
||||
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<Message, FindMessagesParams> {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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<MessageID>
|
||||
removeMessage(id: MessageID): Promise<void>
|
||||
createPatch(message: MessageID, content: RichText, creator: SocialID): Promise<void>
|
||||
createMessage(thread: ThreadID, content: RichText, creator: SocialID): Promise<MessageID>
|
||||
removeMessage(thread: ThreadID, id: MessageID): Promise<void>
|
||||
createPatch(thread: ThreadID, message: MessageID, content: RichText, creator: SocialID): Promise<void>
|
||||
|
||||
createReaction(message: MessageID, reaction: string, creator: SocialID): Promise<void>
|
||||
removeReaction(message: MessageID, reaction: string, creator: SocialID): Promise<void>
|
||||
createReaction(thread: ThreadID, message: MessageID, reaction: string, creator: SocialID): Promise<void>
|
||||
removeReaction(thread: ThreadID, message: MessageID, reaction: string, creator: SocialID): Promise<void>
|
||||
|
||||
createAttachment(message: MessageID, card: CardID, creator: SocialID): Promise<void>
|
||||
removeAttachment(message: MessageID, card: CardID): Promise<void>
|
||||
createAttachment(thread: ThreadID, message: MessageID, card: CardID, creator: SocialID): Promise<void>
|
||||
removeAttachment(thread: ThreadID, message: MessageID, card: CardID): Promise<void>
|
||||
|
||||
createNotification(message: MessageID, context: ContextID): Promise<void>
|
||||
removeNotification(message: MessageID, context: ContextID): Promise<void>
|
||||
|
||||
@@ -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<MessageID>
|
||||
createMessage(
|
||||
workspace: string,
|
||||
thread: ThreadID,
|
||||
content: RichText,
|
||||
creator: SocialID,
|
||||
created: Date
|
||||
): Promise<MessageID>
|
||||
removeMessage(id: MessageID): Promise<void>
|
||||
|
||||
placeMessage(message: MessageID, card: CardID, workspace: string): Promise<void>
|
||||
createPatch(message: MessageID, content: RichText, creator: SocialID, created: Date): Promise<void>
|
||||
|
||||
createReaction(message: MessageID, reaction: string, creator: SocialID, created: Date): Promise<void>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -73,10 +73,10 @@ export class EventProcessor {
|
||||
|
||||
private async createMessage(event: CreateMessageEvent): Promise<Result> {
|
||||
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<Result> {
|
||||
const id = await this.db.createContext(
|
||||
this.personWorkspace,
|
||||
this.workspace,
|
||||
event.card,
|
||||
this.personWorkspace,
|
||||
event.lastView,
|
||||
event.lastUpdate
|
||||
)
|
||||
|
||||
@@ -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<ConnectionInfo> {
|
||||
}
|
||||
|
||||
const personWorkspace = 'cd0aba36-1c4f-4170-95f2-27a12a5415f7'
|
||||
return { workspace: info.workspaceId, personWorkspace, socialId: email }
|
||||
return { workspace: info.workspaceId, personWorkspace, socialId: email as SocialID }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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<BroadcastEvent[]> {
|
||||
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,
|
||||
|
||||
@@ -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<MessageID> {
|
||||
return await this.message.createMessage(content, creator, created)
|
||||
}
|
||||
|
||||
async placeMessage(message: MessageID, card: CardID, workspace: string): Promise<void> {
|
||||
return await this.message.placeMessage(message, card, workspace)
|
||||
async createMessage(
|
||||
workspace: string,
|
||||
thread: ThreadID,
|
||||
content: RichText,
|
||||
creator: SocialID,
|
||||
created: Date
|
||||
): Promise<MessageID> {
|
||||
return await this.message.createMessage(workspace, thread, content, creator, created)
|
||||
}
|
||||
|
||||
async createPatch(message: MessageID, content: RichText, creator: SocialID, created: Date): Promise<void> {
|
||||
|
||||
@@ -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<MessageID> {
|
||||
async createMessage(workspace: string, thread: ThreadID, content: RichText, creator: SocialID, created: Date): Promise<MessageID> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<Message[]> {
|
||||
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),
|
||||
|
||||
@@ -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<void> {
|
||||
@@ -80,7 +80,13 @@ export class NotificationsDb extends BaseDb {
|
||||
|
||||
async findContexts(params: FindNotificationContextParams, personWorkspaces: string[], workspace?: string,): Promise<NotificationContext[]> {
|
||||
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<Notification[]> {
|
||||
//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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
);
|
||||
`
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user