Object restore tool (#8318)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2025-03-23 18:47:03 +05:00 committed by GitHub
parent d689cff362
commit cd97f6fd67
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 74 additions and 1 deletions

View File

@ -78,7 +78,7 @@ import { buildStorageFromConfig, createStorageFromConfig, storageConfigFromEnv }
import { program, type Command } from 'commander'
import { addControlledDocumentRank } from './qms'
import { clearTelegramHistory } from './telegram'
import { backupRestore, diffWorkspace, updateField } from './workspace'
import { backupRestore, diffWorkspace, restoreRemovedDoc, updateField } from './workspace'
import core, {
AccountRole,
@ -2296,6 +2296,15 @@ export function devTool (
})
})
program
.command('restore-removed-doc <workspace>')
.requiredOption('--ids <objectIds>', 'ids')
.action(async (workspace: string, cmd: { ids: string }) => {
const wsid = getWorkspaceId(workspace)
const endpoint = await getTransactorEndpoint(generateToken(systemAccountEmail, wsid), 'external')
await restoreRemovedDoc(toolCtx, wsid, endpoint, cmd.ids)
})
program
.command('add-controlled-doc-rank-mongo')
.description('add rank to controlled documents')

View File

@ -18,6 +18,7 @@ import contact from '@hcengineering/contact'
import core, {
DOMAIN_TX,
getWorkspaceId,
TxOperations,
type BackupClient,
type BaseWorkspaceInfo,
type Class,
@ -161,3 +162,66 @@ export async function backupRestore (
await storageAdapter.close()
}
}
export async function restoreRemovedDoc (
ctx: MeasureContext,
workspaceId: WorkspaceId,
transactorUrl: string,
idsVal: string
): Promise<void> {
const ids = idsVal.split(';').map((it) => it.trim()) as Ref<Doc>[]
const connection = (await connect(transactorUrl, workspaceId, undefined, {
mode: 'backup',
model: 'upgrade', // Required for force all clients reload after operation will be complete.
admin: 'true'
})) as unknown as CoreClient & BackupClient
try {
for (const id of ids) {
try {
ctx.info('start restoring', { id })
const ops = new TxOperations(connection, core.account.System)
const processed = new Set<Ref<Doc>>()
const txes = await getObjectTxesAndRelatedTxes(ctx, ops, id, processed, true)
txes.filter((p) => p._class !== core.class.TxRemoveDoc).sort((a, b) => a.modifiedOn - b.modifiedOn)
for (const tx of txes) {
tx.space = core.space.DerivedTx
await ops.tx(tx)
}
ctx.info('success restored', { id })
} catch (err) {
ctx.error('error restoring', { id, err })
}
}
} finally {
await connection.sendForceClose()
await connection.close()
}
}
async function getObjectTxesAndRelatedTxes (
ctx: MeasureContext,
client: TxOperations,
objectId: Ref<Doc>,
processed: Set<Ref<Doc>>,
filterRemoved = false
): Promise<Tx[]> {
ctx.info('Find txes for', { objectId })
const result: Tx[] = []
if (processed.has(objectId)) {
return result
}
processed.add(objectId)
let txes = (await client.findAll(core.class.TxCUD, { objectId })) as Tx[]
if (filterRemoved) {
txes = txes.filter((it) => it._class !== core.class.TxRemoveDoc)
}
result.push(...txes)
const relatedTxes = await client.findAll(core.class.TxCUD, { attachedTo: objectId })
result.push(...relatedTxes)
const relatedIds = new Set(relatedTxes.map((it) => it.objectId))
for (const relatedId of relatedIds) {
const rel = await getObjectTxesAndRelatedTxes(ctx, client, relatedId, processed)
result.push(...rel)
}
return result
}