diff --git a/dev/tool/src/clean.ts b/dev/tool/src/clean.ts index 29ddfb8e20..bea7d99aeb 100644 --- a/dev/tool/src/clean.ts +++ b/dev/tool/src/clean.ts @@ -14,8 +14,21 @@ // import attachment from '@hcengineering/attachment' +import chunter, { Comment } from '@hcengineering/chunter' import contact from '@hcengineering/contact' -import core, { BackupClient, Client as CoreClient, DOMAIN_TX, TxOperations, WorkspaceId } from '@hcengineering/core' +import core, { + BackupClient, + Client as CoreClient, + DOMAIN_TX, + Doc, + Domain, + Ref, + TxCreateDoc, + TxOperations, + TxProcessor, + WorkspaceId, + generateId +} from '@hcengineering/core' import { MinioService } from '@hcengineering/minio' import { getWorkspaceDB } from '@hcengineering/mongo' import recruit from '@hcengineering/recruit' @@ -23,6 +36,8 @@ import { connect } from '@hcengineering/server-tool' import tracker from '@hcengineering/tracker' import { MongoClient } from 'mongodb' +export const DOMAIN_COMMENT = 'comment' as Domain + export async function cleanWorkspace ( mongoUrl: string, workspaceId: WorkspaceId, @@ -204,3 +219,54 @@ export async function cleanArchivedSpaces (workspaceId: WorkspaceId, transactorU await connection.close() } } + +export async function fixCommentDoubleIdCreate (workspaceId: WorkspaceId, transactorUrl: string): Promise { + const connection = (await connect(transactorUrl, workspaceId, undefined, { + mode: 'backup' + })) as unknown as CoreClient & BackupClient + try { + const commentTxes = await connection.findAll(core.class.TxCollectionCUD, { + 'tx._class': core.class.TxCreateDoc, + 'tx.objectClass': chunter.class.Comment + }) + const commentTxesRemoved = await connection.findAll(core.class.TxCollectionCUD, { + 'tx._class': core.class.TxRemoveDoc, + 'tx.objectClass': chunter.class.Comment + }) + const removed = new Map(commentTxesRemoved.map((it) => [it.tx.objectId, it])) + // Do not checked removed + const objSet = new Set>() + const oldValue = new Map, string>() + for (const c of commentTxes) { + const cid = c.tx.objectId + if (removed.has(cid)) { + continue + } + const has = objSet.has(cid) + objSet.add(cid) + if (has) { + // We have found duplicate one, let's rename it. + const doc = TxProcessor.createDoc2Doc(c.tx as unknown as TxCreateDoc) + if (doc.message !== '' && doc.message.trim() !== '

') { + await connection.clean(DOMAIN_TX, [c._id]) + if (oldValue.get(cid) === doc.message.trim()) { + console.log('delete tx', cid, doc.message) + } else { + oldValue.set(doc._id, doc.message) + console.log('renaming', cid, doc.message) + // Remove previous transaction. + c.tx.objectId = generateId() + doc._id = c.tx.objectId as Ref + await connection.upload(DOMAIN_TX, [c]) + // Also we need to create snapsot + await connection.upload(DOMAIN_COMMENT, [doc]) + } + } + } + } + } catch (err: any) { + console.trace(err) + } finally { + await connection.close() + } +} diff --git a/dev/tool/src/index.ts b/dev/tool/src/index.ts index 4e067f9716..8c1f441988 100644 --- a/dev/tool/src/index.ts +++ b/dev/tool/src/index.ts @@ -52,7 +52,7 @@ import { MinioService } from '@hcengineering/minio' import { MigrateOperation } from '@hcengineering/model' import { openAIConfigDefaults } from '@hcengineering/openai' import { benchmark } from './benchmark' -import { cleanArchivedSpaces, cleanRemovedTransactions, cleanWorkspace } from './clean' +import { cleanArchivedSpaces, cleanRemovedTransactions, cleanWorkspace, fixCommentDoubleIdCreate } from './clean' import { changeConfiguration } from './configuration' import { openAIConfig } from './openai' @@ -455,6 +455,13 @@ export function devTool ( await cleanArchivedSpaces(getWorkspaceId(workspace, productId), transactorUrl) }) + program + .command('chunter-fix-comments ') + .description('chunter-fix-comments') + .action(async (workspace: string, cmd: any) => { + await fixCommentDoubleIdCreate(getWorkspaceId(workspace, productId), transactorUrl) + }) + program .command('configure ') .description('clean archived spaces') diff --git a/plugins/chunter-resources/src/components/CommentInput.svelte b/plugins/chunter-resources/src/components/CommentInput.svelte index 5af1071e4b..384e6d7b41 100644 --- a/plugins/chunter-resources/src/components/CommentInput.svelte +++ b/plugins/chunter-resources/src/components/CommentInput.svelte @@ -17,7 +17,7 @@ import { AttachmentRefInput } from '@hcengineering/attachment-resources' import { Comment } from '@hcengineering/chunter' import { AttachedData, Doc, generateId, Ref } from '@hcengineering/core' - import { DraftController, draftsStore, getClient } from '@hcengineering/presentation' + import { createQuery, DraftController, draftsStore, getClient } from '@hcengineering/presentation' import { createBacklinks } from '../backlinks' import chunter from '../plugin' @@ -40,10 +40,22 @@ let commentInputBox: AttachmentRefInput const draftComment = shouldSaveDraft ? $draftsStore[draftKey] : undefined + let comment: CommentDraft = draftComment ?? getDefault() let _id: Ref = comment._id let inputContent: string = comment.message + const createdQuery = createQuery() + + $: createdQuery.query(chunter.class.Comment, { _id }, (docs) => { + if (docs.length > 0) { + // Ouch we have got comment with same id created already. + comment = getDefault() + _id = comment._id + commentInputBox.removeDraft(false) + } + }) + function objectChange (object: CommentDraft, empty: any) { if (shouldSaveDraft) { draftController.save(object, empty) @@ -98,8 +110,8 @@ await createBacklinks(client, object._id, object._class, _id, message) // Remove draft from Local Storage - _id = generateId() comment = getDefault() + _id = comment._id } catch (err) { console.error(err) } finally {