diff --git a/dev/tool/src/markup.ts b/dev/tool/src/markup.ts index 6aa9e1b1a6..d939e2cb48 100644 --- a/dev/tool/src/markup.ts +++ b/dev/tool/src/markup.ts @@ -20,8 +20,10 @@ import core, { type MeasureContext, type Ref, type TxCreateDoc, + type TxUpdateDoc, type WorkspaceId, DOMAIN_TX, + SortingOrder, makeCollabYdocId, makeDocCollabId } from '@hcengineering/core' @@ -63,13 +65,18 @@ export async function restoreWikiContentMongo ( } const correctCollabId = { objectClass: doc._class, objectId: doc._id, objectAttr: 'content' } - const wrongCollabId = { objectClass: doc._class, objectId: doc._id, objectAttr: 'description' } - const stat = storageAdapter.stat(ctx, workspaceId, makeCollabYdocId(wrongCollabId)) + const wrongYdocId = await findWikiDocYdocName(ctx, db, workspaceId, doc._id) + if (wrongYdocId === undefined) { + console.log('current ydoc not found', doc._id) + continue + } + + const stat = storageAdapter.stat(ctx, workspaceId, wrongYdocId) if (stat === undefined) continue const ydoc1 = await loadCollabYdoc(ctx, storageAdapter, workspaceId, correctCollabId) - const ydoc2 = await loadCollabYdoc(ctx, storageAdapter, workspaceId, wrongCollabId) + const ydoc2 = await loadCollabYdoc(ctx, storageAdapter, workspaceId, wrongYdocId) if (ydoc1 !== undefined && ydoc1.share.has('content')) { // There already is content, we should skip the document @@ -101,6 +108,81 @@ export async function restoreWikiContentMongo ( } } +export async function findWikiDocYdocName ( + ctx: MeasureContext, + db: Db, + workspaceId: WorkspaceId, + doc: Ref +): Promise | undefined> { + const updateContentTx = await db.collection>(DOMAIN_TX).findOne( + { + _class: core.class.TxUpdateDoc, + objectId: doc, + objectClass: document.class.Document, + 'operations.content': { $exists: true } + }, + { + sort: { modifiedOn: SortingOrder.Descending } + } + ) + + if (updateContentTx?.operations?.content != null) { + const value = updateContentTx.operations.content as string + if (value.includes(':')) { + console.log('found update content tx', doc, value) + return value.split(':')[0] as Ref + } + } + + const updateDescriptionTx = await db.collection>(DOMAIN_TX).findOne( + { + _class: core.class.TxUpdateDoc, + objectId: doc, + objectClass: document.class.Document, + 'operations.description': { $exists: true } + }, + { + sort: { modifiedOn: SortingOrder.Descending } + } + ) + + if (updateDescriptionTx?.operations?.description != null) { + const value = updateDescriptionTx.operations.description + if (value.includes(':')) { + console.log('found update description tx', doc, value) + return value.split(':')[0] as Ref + } + } + + const createContentTx = await db.collection>(DOMAIN_TX).findOne({ + _class: core.class.TxCreateDoc, + objectId: doc, + objectClass: document.class.Document, + 'attributes.content': { $exists: true } + }) + + if (createContentTx?.attributes?.content != null) { + const value = createContentTx.attributes.content + if (value.includes(':')) { + console.log('found create content tx', doc, value) + return value.split(':')[0] as Ref + } + } + + const createContentIdTx = await db.collection }>>(DOMAIN_TX).findOne({ + _class: core.class.TxCreateDoc, + objectId: doc, + objectClass: document.class.Document, + 'attributes.contentId': { $exists: true } + }) + + if (createContentIdTx?.attributes?.contentId != null) { + const value = createContentIdTx.attributes.contentId + console.log('found create contentId tx', doc, value) + return value + } +} + export interface RestoreControlledDocContentParams { dryRun: boolean } diff --git a/packages/core/src/collaboration.ts b/packages/core/src/collaboration.ts index 1042ef561c..ecf14a96b9 100644 --- a/packages/core/src/collaboration.ts +++ b/packages/core/src/collaboration.ts @@ -13,7 +13,7 @@ // limitations under the License. // -import type { Class, MarkupBlobRef, Doc, Ref } from './classes' +import type { Blob, Class, Doc, MarkupBlobRef, Ref } from './classes' /** @public */ export interface CollaborativeDoc { @@ -40,9 +40,9 @@ export function makeDocCollabId ( } /** @public */ -export function makeCollabYdocId (doc: CollaborativeDoc): MarkupBlobRef { +export function makeCollabYdocId (doc: CollaborativeDoc): Ref { const { objectId, objectAttr } = doc - return `${objectId}%${objectAttr}` as MarkupBlobRef + return `${objectId}%${objectAttr}` as Ref } /** @public */ diff --git a/server/collaboration/src/storage.ts b/server/collaboration/src/storage.ts index d723227ae0..529577b3db 100644 --- a/server/collaboration/src/storage.ts +++ b/server/collaboration/src/storage.ts @@ -19,6 +19,7 @@ import { type Ref, type WorkspaceId, Markup, + MarkupBlobRef, MeasureContext, generateId, makeCollabJsonId, @@ -35,9 +36,9 @@ export async function loadCollabYdoc ( ctx: MeasureContext, storageAdapter: StorageAdapter, workspace: WorkspaceId, - doc: CollaborativeDoc + doc: CollaborativeDoc | MarkupBlobRef ): Promise { - const blobId = makeCollabYdocId(doc) + const blobId = typeof doc === 'string' ? doc : makeCollabYdocId(doc) const blob = await storageAdapter.stat(ctx, workspace, blobId) if (blob === undefined) { @@ -61,10 +62,10 @@ export async function saveCollabYdoc ( ctx: MeasureContext, storageAdapter: StorageAdapter, workspace: WorkspaceId, - doc: CollaborativeDoc, + doc: CollaborativeDoc | MarkupBlobRef, ydoc: YDoc ): Promise> { - const blobId = makeCollabYdocId(doc) + const blobId = typeof doc === 'string' ? doc : makeCollabYdocId(doc) const buffer = yDocToBuffer(ydoc) await storageAdapter.put(ctx, workspace, blobId, buffer, 'application/ydoc', buffer.length) diff --git a/server/s3/src/index.ts b/server/s3/src/index.ts index 2801aef391..f3ab7cd956 100644 --- a/server/s3/src/index.ts +++ b/server/s3/src/index.ts @@ -307,7 +307,9 @@ export class S3Service implements StorageAdapter { version: result.VersionId ?? null } } catch (err: any) { - ctx.warn('no object found', { error: err, objectName, workspaceId: workspaceId.name }) + if (err?.$metadata?.httpStatusCode !== 404) { + ctx.warn('no object found', { error: err, objectName, workspaceId: workspaceId.name }) + } } }