Add update blob event (#81)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina
2025-07-04 14:37:43 +04:00
committed by GitHub
parent 8d50243ed2
commit ceefe63ef1
9 changed files with 136 additions and 9 deletions
+11 -1
View File
@@ -45,7 +45,7 @@ import {
type LinkPreviewData,
type LinkPreviewID,
type MessageExtra,
type BlobData
type BlobData, BlobUpdateData
} from '@hcengineering/communication-types'
import type {
DbAdapter,
@@ -162,6 +162,16 @@ export class CockroachAdapter implements DbAdapter {
await this.message.setBlobs(cardId, messageId, blobs, socialId, date)
}
async updateBlobs (
cardId: CardID,
messageId: MessageID,
blobs: BlobUpdateData[],
socialId: SocialID,
date: Date
): Promise<void> {
await this.message.updateBlobs(cardId, messageId, blobs, socialId, date)
}
async attachLinkPreviews (
cardId: CardID,
messageId: MessageID,
+61
View File
@@ -20,6 +20,7 @@ import {
AttachThreadPatchData,
type BlobData,
type BlobID,
BlobUpdateData,
type CardID,
type CardType,
DetachBlobsPatchData,
@@ -40,6 +41,7 @@ import {
type SocialID,
SortingOrder,
type Thread,
UpdateBlobsPatchData,
UpdateThreadPatchData
} from '@hcengineering/communication-types'
import type { ThreadUpdates, ThreadQuery } from '@hcengineering/communication-sdk-types'
@@ -298,6 +300,65 @@ export class MessagesDb extends BaseDb {
})
}
async updateBlobs (
cardId: CardID,
messageId: MessageID,
blobs: BlobUpdateData[],
socialId: SocialID,
date: Date
): Promise<void> {
if (blobs.length === 0) return
const colMap = {
mimeType: { col: 'type', cast: '::varchar' },
fileName: { col: 'filename', cast: '::varchar' },
size: { col: 'size', cast: '::int8' },
metadata: { col: 'meta', cast: '::jsonb' }
} as const
type UpdateKey = keyof typeof colMap
const updateKeys = Object.keys(colMap) as UpdateKey[]
const params: any[] = [this.workspace, cardId, messageId]
const rowLen = 1 + updateKeys.length
const tuples = blobs.map((blob, i) => {
params.push(blob.blobId)
updateKeys.forEach((k) => params.push(blob[k] ?? null))
const offset = 3 + i * rowLen
const casts = ['::uuid', ...updateKeys.map((k) => colMap[k].cast)]
const placeholders = casts.map((cast, idx) => `$${offset + idx + 1}${cast}`)
return `(${placeholders.join(', ')})`
})
const setClauses = updateKeys.map((k) => {
const col = colMap[k].col
return `${col} = COALESCE(v.${col}, f.${col})`
})
const updateSql = `
UPDATE ${TableName.File} AS f
SET ${setClauses.join(',\n ')}
FROM (VALUES ${tuples.join(',\n ')}) AS v(blob_id, ${updateKeys.map((k) => colMap[k].col).join(', ')})
WHERE f.workspace_id = $1::uuid
AND f.card_id = $2::varchar
AND f.message_id = $3::varchar
AND f.blob_id = v.blob_id;
`
const inDb = await this.isMessageInDb(cardId, messageId)
if (!inDb) {
await this.getRowClient().begin(async (txn) => {
await this.execute(updateSql, params, 'update blobs', txn)
const data: UpdateBlobsPatchData = { operation: 'update', blobs }
await this.createPatch(cardId, messageId, PatchType.blob, data, socialId, date, txn)
})
} else {
await this.execute(updateSql, params, 'update blobs')
}
}
async attachLinkPreviews (
cardId: CardID,
messageId: MessageID,
+2 -1
View File
@@ -44,7 +44,7 @@ import {
NotificationType,
BlobData,
LinkPreviewData,
LinkPreviewID
LinkPreviewID, BlobUpdateData
} from '@hcengineering/communication-types'
export interface DbAdapter {
@@ -76,6 +76,7 @@ export interface DbAdapter {
attachBlobs: (cardId: CardID, messageId: MessageID, data: BlobData[], socialId: SocialID, date: Date) => Promise<void>
detachBlobs: (card: CardID, messageId: MessageID, blobId: BlobID[], socialId: SocialID, date: Date) => Promise<void>
setBlobs: (cardId: CardID, messageId: MessageID, data: BlobData[], socialId: SocialID, date: Date) => Promise<void>
updateBlobs: (cardId: CardID, messageId: MessageID, data: BlobUpdateData[], socialId: SocialID, date: Date) => Promise<void>
attachLinkPreviews: (
cardId: CardID,
+8 -2
View File
@@ -10,7 +10,8 @@ import type {
MessagesGroup,
MessageExtra,
BlobData,
LinkPreviewData
LinkPreviewData,
BlobUpdateData
} from '@hcengineering/communication-types'
import type { BaseEvent } from './common'
@@ -134,6 +135,11 @@ export interface SetBlobsOperation {
blobs: BlobData[]
}
export interface UpdateBlobsOperation {
opcode: 'update'
blobs: BlobUpdateData[]
}
// For system and message author
export interface BlobPatchEvent extends BaseEvent {
type: MessageEventType.BlobPatch
@@ -141,7 +147,7 @@ export interface BlobPatchEvent extends BaseEvent {
cardId: CardID
messageId: MessageID
operations: (AttachBlobsOperation | DetachBlobsOperation | SetBlobsOperation)[]
operations: (AttachBlobsOperation | DetachBlobsOperation | SetBlobsOperation | UpdateBlobsOperation)[]
socialId: SocialID
date?: Date
+2
View File
@@ -271,6 +271,8 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
await this.db.detachBlobs(event.cardId, event.messageId, operation.blobIds, event.socialId, event.date)
} else if (operation.opcode === 'set') {
await this.db.setBlobs(event.cardId, event.messageId, operation.blobs, event.socialId, event.date)
} else if (operation.opcode === 'update') {
await this.db.updateBlobs(event.cardId, event.messageId, operation.blobs, event.socialId, event.date)
}
}
+10 -1
View File
@@ -167,6 +167,14 @@ const BlobDataSchema = z.object({
metadata: z.record(z.string(), z.any()).optional()
})
const UpdateBlobDataSchema = z.object({
blobId: BlobIDSchema,
mimeType: z.string().optional(),
fileName: z.string().optional(),
size: z.number().optional(),
metadata: z.record(z.string(), z.any()).optional()
})
const LinkPreviewDataSchema = z
.object({
previewId: LinkPreviewIDSchema,
@@ -329,7 +337,8 @@ const ReactionPatchEventSchema = BaseEventSchema.extend({
const BlobOperationSchema = z.union([
z.object({ opcode: z.literal('attach'), blobs: z.array(BlobDataSchema).nonempty() }),
z.object({ opcode: z.literal('detach'), blobIds: z.array(BlobIDSchema).nonempty() }),
z.object({ opcode: z.literal('set'), blobs: z.array(BlobDataSchema).nonempty() })
z.object({ opcode: z.literal('set'), blobs: z.array(BlobDataSchema).nonempty() }),
z.object({ opcode: z.literal('update'), blobs: z.array(UpdateBlobDataSchema).nonempty() })
])
const BlobPatchEventSchema = BaseEventSchema.extend({
+25 -1
View File
@@ -28,7 +28,8 @@ import {
PatchType,
ReactionPatch,
SocialID,
ThreadPatch
ThreadPatch,
BlobUpdateData
} from '@hcengineering/communication-types'
export function applyPatches (message: Message, patches: Patch[], allowedPatchTypes: PatchType[] = []): Message {
@@ -85,6 +86,8 @@ function patchBlobs (message: Message, patch: BlobPatch): Message {
return detachBlobs(message, patch.data.blobIds)
} else if (patch.data.operation === 'set') {
return setBlobs(message, patch.data.blobs, patch.created, patch.creator)
} else if (patch.data.operation === 'update') {
return updateBlobs(message, patch.data.blobs)
}
return message
}
@@ -149,6 +152,27 @@ function attachBlobs (message: Message, data: BlobData[], created: Date, creator
}
}
function updateBlobs (message: Message, updates: BlobUpdateData[]): Message {
if (updates.length === 0) return message
const updatedBlobs = []
for (const blob of message.blobs) {
const update = updates.find((it) => it.blobId === blob.blobId)
if (update === undefined) {
updatedBlobs.push(blob)
} else {
updatedBlobs.push({
...blob,
...update
})
}
}
return {
...message,
blobs: updatedBlobs
}
}
function detachBlobs (message: Message, blobIds: BlobID[]): Message {
const blobs = message.blobs.filter((it) => !blobIds.includes(it.blobId))
if (blobs.length === message.blobs.length) return message
+9 -2
View File
@@ -27,6 +27,7 @@ import {
PatchType,
SetBlobsPatchData,
SetLinkPreviewsPatchData,
UpdateBlobsPatchData,
UpdateThreadPatchData
} from '@hcengineering/communication-types'
import {
@@ -43,6 +44,7 @@ import {
RemoveNotificationContextEvent,
SetBlobsOperation,
SetLinkPreviewsOperation,
UpdateBlobsOperation,
UpdateNotificationContextEvent,
UpdateThreadOperation
} from '@hcengineering/communication-sdk-types'
@@ -216,8 +218,8 @@ export class NotificationProcessor {
}
function blobOperationToPatchData (
operation: AttachBlobsOperation | DetachBlobsOperation | SetBlobsOperation
): AttachBlobsPatchData | DetachBlobsPatchData | SetBlobsPatchData | undefined {
operation: AttachBlobsOperation | DetachBlobsOperation | SetBlobsOperation | UpdateBlobsOperation
): AttachBlobsPatchData | DetachBlobsPatchData | SetBlobsPatchData | UpdateBlobsPatchData | undefined {
if (operation.opcode === 'attach') {
return {
operation: 'attach',
@@ -233,6 +235,11 @@ function blobOperationToPatchData (
operation: 'set',
blobs: operation.blobs
}
} else if (operation.opcode === 'update') {
return {
operation: 'update',
blobs: operation.blobs
}
}
return undefined
+8 -1
View File
@@ -151,7 +151,7 @@ export interface RemoveReactionPatchData {
export interface BlobPatch extends BasePatch {
type: PatchType.blob
data: AttachBlobsPatchData | DetachBlobsPatchData | SetBlobsPatchData
data: AttachBlobsPatchData | DetachBlobsPatchData | SetBlobsPatchData | UpdateBlobsPatchData
}
export interface AttachBlobsPatchData {
@@ -169,6 +169,11 @@ export interface SetBlobsPatchData {
blobs: BlobData[]
}
export interface UpdateBlobsPatchData {
operation: 'update'
blobs: BlobUpdateData[]
}
export interface LinkPreviewPatch extends BasePatch {
type: PatchType.linkPreview
data: AttachLinkPreviewsPatchData | DetachLinkPreviewsPatchData | SetLinkPreviewsPatchData
@@ -233,6 +238,8 @@ export interface BlobData {
metadata?: BlobMetadata
}
export type BlobUpdateData = { blobId: BlobID } & Partial<BlobData>
export interface AttachedBlob extends BlobData {
creator: SocialID
created: Date