From 2b2069372affeb32500137205cbcf740a10bb4ae Mon Sep 17 00:00:00 2001 From: Sergei Samokhvalov Date: Tue, 28 Nov 2023 16:18:00 +0300 Subject: [PATCH 01/11] add new route, controller for restore workbook --- src/controllers/workbooks.ts | 19 +++ src/routes.ts | 8 ++ .../formatters/format-restore-workbook.ts | 7 + src/services/new/workbook/formatters/index.ts | 1 + src/services/new/workbook/index.ts | 1 + src/services/new/workbook/restore-workbook.ts | 134 ++++++++++++++++++ 6 files changed, 170 insertions(+) create mode 100644 src/services/new/workbook/formatters/format-restore-workbook.ts create mode 100644 src/services/new/workbook/restore-workbook.ts diff --git a/src/controllers/workbooks.ts b/src/controllers/workbooks.ts index faecf41c..8815fe87 100644 --- a/src/controllers/workbooks.ts +++ b/src/controllers/workbooks.ts @@ -16,6 +16,7 @@ import { getAllWorkbooks, OrderField, OrderDirection, + restoreWorkbook, } from '../services/new/workbook'; import { formatWorkbookModel, @@ -25,6 +26,7 @@ import { formatWorkbookModelWithOperation, formatWorkbooksList, formatSetWorkbookIsTemplate, + formatRestoreWorkbook, } from '../services/new/workbook/formatters'; export default { @@ -260,4 +262,21 @@ export default { const {code, response} = prepareResponse({data: formattedResponse}); res.status(code).send(response); }, + + restore: async (req: Request, res: Response) => { + const {params} = req; + + const result = await restoreWorkbook( + { + ctx: req.ctx, + }, + { + workbookId: params.workbookId, + }, + ); + + const formattedResponse = formatRestoreWorkbook(result); + const {code, response} = prepareResponse({data: formattedResponse}); + res.status(code).send(response); + }, }; diff --git a/src/routes.ts b/src/routes.ts index f8a16b2c..18280a7e 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -333,6 +333,14 @@ export function getRoutes(nodekit: NodeKit, options: GetRoutesOptions) { write: true, }), + privateRestoreWorkbook: makeRoute({ + route: 'POST /private/v2/workbooks/:workbookId/restore', + handler: workbooksController.restore, + authPolicy: AuthPolicy.disabled, + private: true, + write: true, + }), + privateGetAllWorkbooks: makeRoute({ route: 'GET /private/all-workbooks', handler: workbooksController.getAll, diff --git a/src/services/new/workbook/formatters/format-restore-workbook.ts b/src/services/new/workbook/formatters/format-restore-workbook.ts new file mode 100644 index 00000000..d5de9bed --- /dev/null +++ b/src/services/new/workbook/formatters/format-restore-workbook.ts @@ -0,0 +1,7 @@ +import {WorkbookModel} from '../../../../db/models/new/workbook'; + +export const formatRestoreWorkbook = (workbookModel: WorkbookModel) => { + return { + workbookId: workbookModel.workbookId, + }; +}; diff --git a/src/services/new/workbook/formatters/index.ts b/src/services/new/workbook/formatters/index.ts index 9af0d06f..3a0ffea0 100644 --- a/src/services/new/workbook/formatters/index.ts +++ b/src/services/new/workbook/formatters/index.ts @@ -5,3 +5,4 @@ export * from './format-workbook-model'; export * from './format-workbook-model-with-operation'; export * from './format-workbook-models-list'; export * from './format-set-workbook-is-template'; +export * from './format-restore-workbook'; diff --git a/src/services/new/workbook/index.ts b/src/services/new/workbook/index.ts index 21222f7d..ca0629b8 100644 --- a/src/services/new/workbook/index.ts +++ b/src/services/new/workbook/index.ts @@ -11,3 +11,4 @@ export * from './set-workbook-is-template'; export * from './copy-workbook'; export * from './copy-workbook-template'; export * from './get-all-workbooks'; +export * from './restore-workbook'; diff --git a/src/services/new/workbook/restore-workbook.ts b/src/services/new/workbook/restore-workbook.ts new file mode 100644 index 00000000..483fcda9 --- /dev/null +++ b/src/services/new/workbook/restore-workbook.ts @@ -0,0 +1,134 @@ +import {transaction, raw} from 'objection'; +import {AppError} from '@gravity-ui/nodekit'; +import {ServiceArgs} from '../types'; +import {getReplica, getPrimary} from '../utils'; +import {makeSchemaValidator} from '../../../components/validation-schema-compiler'; +import {WorkbookModel, WorkbookModelColumn} from '../../../db/models/new/workbook'; +import Utils, {logInfo} from '../../../utils'; +import {Entry, EntryColumn} from '../../../db/models/new/entry'; + +import {US_ERRORS, CURRENT_TIMESTAMP, DEFAULT_QUERY_TIMEOUT, TRASH_FOLDER} from '../../../const'; + +const validateArgs = makeSchemaValidator({ + type: 'object', + required: ['workbookId'], + properties: { + workbookId: { + type: 'string', + }, + }, +}); + +export interface RestoreWorkbookArgs { + workbookId: string; +} + +export const restoreWorkbook = async ( + {ctx, trx, skipValidation = false}: ServiceArgs, + args: RestoreWorkbookArgs, +) => { + const {workbookId} = args; + + logInfo(ctx, 'RESTORE_WORKBOOK_START', { + workbookId: Utils.encodeId(workbookId), + }); + + if (!skipValidation) { + validateArgs(args); + } + + const {tenantId} = ctx.get('info'); + + const targetTrx = getReplica(trx); + + const model: Optional = await WorkbookModel.query(targetTrx) + .skipUndefined() + .select() + .where({ + [WorkbookModelColumn.TenantId]: tenantId, + [WorkbookModelColumn.WorkbookId]: workbookId, + }) + .andWhereNot(WorkbookModelColumn.DeletedAt, null) + .first() + .timeout(WorkbookModel.DEFAULT_QUERY_TIMEOUT); + + if (!model) { + throw new AppError(US_ERRORS.WORKBOOK_NOT_EXISTS, { + code: US_ERRORS.WORKBOOK_NOT_EXISTS, + }); + } + + console.log('modelasd: ', model); + + const entries = await Entry.query(targetTrx) + .skipUndefined() + .select() + .where({ + [EntryColumn.WorkbookId]: workbookId, + [EntryColumn.TenantId]: tenantId, + [EntryColumn.IsDeleted]: true, + }) + .timeout(Entry.DEFAULT_QUERY_TIMEOUT); + + console.log('etrniess: ', entries); + + const primaryTrx = getPrimary(trx); + + const result = await transaction(primaryTrx, async (transactionTrx) => { + const restoredWorkbook = await WorkbookModel.query(transactionTrx) + .skipUndefined() + .patch({ + [WorkbookModelColumn.DeletedBy]: null, + [WorkbookModelColumn.DeletedAt]: raw(CURRENT_TIMESTAMP), + [WorkbookModelColumn.UpdatedAt]: raw(CURRENT_TIMESTAMP), + }) + .where({ + [WorkbookModelColumn.WorkbookId]: model.workbookId, + [WorkbookModelColumn.TenantId]: tenantId, + }) + .returning('*') + .first() + .timeout(WorkbookModel.DEFAULT_QUERY_TIMEOUT); + + await Promise.all( + entries.map(async (entry) => { + const {entryId, displayKey, key} = entry; + + const newInnerMeta = { + ...entry.innerMeta, + oldKey: key as string, + oldDisplayKey: displayKey as string, + }; + + Entry.query(trx) + .skipUndefined() + .patch({ + key: key?.replace(TRASH_FOLDER, ''), + displayKey: displayKey?.replace(TRASH_FOLDER, ''), + innerMeta: newInnerMeta, + isDeleted: false, + deletedAt: null, + updatedAt: raw(CURRENT_TIMESTAMP), + }) + .where({ + entryId, + }) + .timeout(DEFAULT_QUERY_TIMEOUT); + }), + ); + + return restoredWorkbook; + }); + + if (!result) { + throw new AppError(US_ERRORS.WORKBOOK_NOT_EXISTS, { + code: US_ERRORS.WORKBOOK_NOT_EXISTS, + }); + } + + ctx.log('RESTORE_WORKBOOK_FINISH', { + workbookId: Utils.encodeId(workbookId), + }); + + return result; +}; From a47bbd3c857877034ee55070c15b1b6699d3c928 Mon Sep 17 00:00:00 2001 From: Sergei Samokhvalov Date: Wed, 29 Nov 2023 15:43:08 +0300 Subject: [PATCH 02/11] Added where DeletedAt depends on workbook deletedAt --- src/services/new/workbook/restore-workbook.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/services/new/workbook/restore-workbook.ts b/src/services/new/workbook/restore-workbook.ts index 483fcda9..649b3ef1 100644 --- a/src/services/new/workbook/restore-workbook.ts +++ b/src/services/new/workbook/restore-workbook.ts @@ -42,8 +42,8 @@ export const restoreWorkbook = async ( const targetTrx = getReplica(trx); const model: Optional = await WorkbookModel.query(targetTrx) - .skipUndefined() .select() + .skipUndefined() .where({ [WorkbookModelColumn.TenantId]: tenantId, [WorkbookModelColumn.WorkbookId]: workbookId, @@ -58,11 +58,9 @@ export const restoreWorkbook = async ( }); } - console.log('modelasd: ', model); - const entries = await Entry.query(targetTrx) - .skipUndefined() .select() + .skipUndefined() .where({ [EntryColumn.WorkbookId]: workbookId, [EntryColumn.TenantId]: tenantId, @@ -70,8 +68,6 @@ export const restoreWorkbook = async ( }) .timeout(Entry.DEFAULT_QUERY_TIMEOUT); - console.log('etrniess: ', entries); - const primaryTrx = getPrimary(trx); const result = await transaction(primaryTrx, async (transactionTrx) => { @@ -79,7 +75,7 @@ export const restoreWorkbook = async ( .skipUndefined() .patch({ [WorkbookModelColumn.DeletedBy]: null, - [WorkbookModelColumn.DeletedAt]: raw(CURRENT_TIMESTAMP), + [WorkbookModelColumn.DeletedAt]: null, [WorkbookModelColumn.UpdatedAt]: raw(CURRENT_TIMESTAMP), }) .where({ @@ -100,11 +96,10 @@ export const restoreWorkbook = async ( oldDisplayKey: displayKey as string, }; - Entry.query(trx) - .skipUndefined() + return await Entry.query(transactionTrx) .patch({ - key: key?.replace(TRASH_FOLDER, ''), - displayKey: displayKey?.replace(TRASH_FOLDER, ''), + key: key?.replace(TRASH_FOLDER + '/', ''), + displayKey: displayKey?.replace(TRASH_FOLDER + '/', ''), innerMeta: newInnerMeta, isDeleted: false, deletedAt: null, @@ -113,6 +108,7 @@ export const restoreWorkbook = async ( .where({ entryId, }) + .andWhere(EntryColumn.DeletedAt, '>=', model.deletedAt) .timeout(DEFAULT_QUERY_TIMEOUT); }), ); From ba4335eaa874f42840af9bffa89f4e37e340c1a3 Mon Sep 17 00:00:00 2001 From: Sergei Samokhvalov Date: Wed, 29 Nov 2023 16:39:44 +0300 Subject: [PATCH 03/11] Add int tests --- .../int/common/workbooks.private.test.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/tests/int/common/workbooks.private.test.ts b/src/tests/int/common/workbooks.private.test.ts index 28ccd58b..d6c09aa2 100644 --- a/src/tests/int/common/workbooks.private.test.ts +++ b/src/tests/int/common/workbooks.private.test.ts @@ -140,3 +140,23 @@ describe('Private Entries in workboooks managment', () => { await withScopeHeaders(request(app).delete(`/v2/workbooks/${testWorkbookId}`)).expect(200); }); }); + +describe('Private for one workboook managment', () => { + test('Restore workbook with entries – [POST /private/v2/workbooks/:workbookId/restore]', async () => { + await withScopeHeaders(request(app).delete(`/v2/workbooks/${testWorkbookId}`)).expect(200); + + await request(app).get(`/private/v2/workbooks/${testWorkbookId}/restore`).expect(403); + + const response = await withScopeHeaders( + request(app) + .get(`/private/v2/workbooks/${testWorkbookId}/restore`) + .set({[US_MASTER_TOKEN_HEADER]: usApp.config.masterToken}), + ).expect(200); + + const {body} = response; + + expect(body).toStrictEqual({ + workbookId: testWorkbookId, + }); + }); +}); From e33fbaae41555ac38e6e7764d6e634c824a8cb6e Mon Sep 17 00:00:00 2001 From: Sergei Samokhvalov Date: Wed, 29 Nov 2023 16:45:40 +0300 Subject: [PATCH 04/11] Add praparing for test --- .../int/common/workbooks.private.test.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/tests/int/common/workbooks.private.test.ts b/src/tests/int/common/workbooks.private.test.ts index d6c09aa2..31a61ecf 100644 --- a/src/tests/int/common/workbooks.private.test.ts +++ b/src/tests/int/common/workbooks.private.test.ts @@ -142,6 +142,36 @@ describe('Private Entries in workboooks managment', () => { }); describe('Private for one workboook managment', () => { + test('Create workbook with entries – [POST /private/v2/workbooks]', async () => { + const testTitle = 'Test private workbook with entries title'; + const testDescription = 'Test private workbook with entries description'; + + const entry1Name = 'Entry in test workbook 1'; + + const responseWorkbook = await withScopeHeaders(request(app).post('/private/v2/workbooks')) + .set({[US_MASTER_TOKEN_HEADER]: usApp.config.masterToken}) + .send({ + title: testTitle, + description: testDescription, + }) + .expect(200); + + const {body: bodyWorkbook} = responseWorkbook; + + testWorkbookId = bodyWorkbook.workbookId; + + await withScopeHeaders(request(app).post('/v1/entries')) + .send({ + scope: 'dataset', + type: 'graph', + meta: {}, + data: {}, + name: entry1Name, + workbookId: testWorkbookId, + }) + .expect(200); + }); + test('Restore workbook with entries – [POST /private/v2/workbooks/:workbookId/restore]', async () => { await withScopeHeaders(request(app).delete(`/v2/workbooks/${testWorkbookId}`)).expect(200); From 819ae97ab5a687ef1f20c7b585140020566c34eb Mon Sep 17 00:00:00 2001 From: Sergei Samokhvalov Date: Wed, 29 Nov 2023 16:50:40 +0300 Subject: [PATCH 05/11] Fix test --- src/tests/int/common/workbooks.private.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/int/common/workbooks.private.test.ts b/src/tests/int/common/workbooks.private.test.ts index 31a61ecf..bbfc5ff9 100644 --- a/src/tests/int/common/workbooks.private.test.ts +++ b/src/tests/int/common/workbooks.private.test.ts @@ -175,7 +175,7 @@ describe('Private for one workboook managment', () => { test('Restore workbook with entries – [POST /private/v2/workbooks/:workbookId/restore]', async () => { await withScopeHeaders(request(app).delete(`/v2/workbooks/${testWorkbookId}`)).expect(200); - await request(app).get(`/private/v2/workbooks/${testWorkbookId}/restore`).expect(403); + await request(app).post(`/private/v2/workbooks/${testWorkbookId}/restore`).expect(403); const response = await withScopeHeaders( request(app) From f64bf0c058d189bb14d6c548202cc62dea850a76 Mon Sep 17 00:00:00 2001 From: Sergei Samokhvalov Date: Wed, 29 Nov 2023 16:52:17 +0300 Subject: [PATCH 06/11] Fix test --- src/tests/int/common/workbooks.private.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/int/common/workbooks.private.test.ts b/src/tests/int/common/workbooks.private.test.ts index bbfc5ff9..33988cc0 100644 --- a/src/tests/int/common/workbooks.private.test.ts +++ b/src/tests/int/common/workbooks.private.test.ts @@ -179,7 +179,7 @@ describe('Private for one workboook managment', () => { const response = await withScopeHeaders( request(app) - .get(`/private/v2/workbooks/${testWorkbookId}/restore`) + .post(`/private/v2/workbooks/${testWorkbookId}/restore`) .set({[US_MASTER_TOKEN_HEADER]: usApp.config.masterToken}), ).expect(200); From fff6941e16b1acf50851a3e5cc14a74928c0dd99 Mon Sep 17 00:00:00 2001 From: Sergei Samokhvalov Date: Wed, 29 Nov 2023 16:55:46 +0300 Subject: [PATCH 07/11] Add deleting workbook test --- src/tests/int/common/workbooks.private.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tests/int/common/workbooks.private.test.ts b/src/tests/int/common/workbooks.private.test.ts index 33988cc0..7fe87bfb 100644 --- a/src/tests/int/common/workbooks.private.test.ts +++ b/src/tests/int/common/workbooks.private.test.ts @@ -188,5 +188,7 @@ describe('Private for one workboook managment', () => { expect(body).toStrictEqual({ workbookId: testWorkbookId, }); + + await withScopeHeaders(request(app).delete(`/v2/workbooks/${testWorkbookId}`)).expect(200); }); }); From 22f50db3e0f559c0ea6d34667597f6ac184c5e0d Mon Sep 17 00:00:00 2001 From: Sergei Samokhvalov Date: Wed, 29 Nov 2023 16:58:37 +0300 Subject: [PATCH 08/11] Fix test deleting --- src/tests/int/common/workbooks.private.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tests/int/common/workbooks.private.test.ts b/src/tests/int/common/workbooks.private.test.ts index 7fe87bfb..f1555a6b 100644 --- a/src/tests/int/common/workbooks.private.test.ts +++ b/src/tests/int/common/workbooks.private.test.ts @@ -173,8 +173,6 @@ describe('Private for one workboook managment', () => { }); test('Restore workbook with entries – [POST /private/v2/workbooks/:workbookId/restore]', async () => { - await withScopeHeaders(request(app).delete(`/v2/workbooks/${testWorkbookId}`)).expect(200); - await request(app).post(`/private/v2/workbooks/${testWorkbookId}/restore`).expect(403); const response = await withScopeHeaders( From 76aab08dc57f9333774a183982965084e712047a Mon Sep 17 00:00:00 2001 From: Sergei Samokhvalov Date: Wed, 29 Nov 2023 17:04:36 +0300 Subject: [PATCH 09/11] Add test for getting entries workbook after restore --- .../int/common/workbooks.private.test.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/tests/int/common/workbooks.private.test.ts b/src/tests/int/common/workbooks.private.test.ts index f1555a6b..6c976c22 100644 --- a/src/tests/int/common/workbooks.private.test.ts +++ b/src/tests/int/common/workbooks.private.test.ts @@ -173,6 +173,8 @@ describe('Private for one workboook managment', () => { }); test('Restore workbook with entries – [POST /private/v2/workbooks/:workbookId/restore]', async () => { + await withScopeHeaders(request(app).delete(`/v2/workbooks/${testWorkbookId}`)).expect(200); + await request(app).post(`/private/v2/workbooks/${testWorkbookId}/restore`).expect(403); const response = await withScopeHeaders( @@ -187,6 +189,35 @@ describe('Private for one workboook managment', () => { workbookId: testWorkbookId, }); + const responseEntries = await withScopeHeaders( + request(app) + .get(`/private/v2/workbooks/${testWorkbookId}/entries`) + .set({[US_MASTER_TOKEN_HEADER]: usApp.config.masterToken}), + ).expect(200); + + const {body: bodyEntries} = responseEntries; + + expect(bodyEntries).toStrictEqual({ + entries: expect.arrayContaining([ + { + createdAt: expect.any(String), + createdBy: expect.any(String), + entryId: expect.any(String), + hidden: false, + isLocked: false, + key: expect.any(String), + meta: {}, + publishedId: null, + savedId: expect.any(String), + scope: 'dataset', + type: 'graph', + updatedAt: expect.any(String), + updatedBy: expect.any(String), + workbookId: testWorkbookId, + }, + ]), + }); + await withScopeHeaders(request(app).delete(`/v2/workbooks/${testWorkbookId}`)).expect(200); }); }); From a24a2cfec8f1fbeffa14e6d18f85bea401c56d14 Mon Sep 17 00:00:00 2001 From: Sergei Samokhvalov Date: Wed, 6 Dec 2023 09:58:41 +0300 Subject: [PATCH 10/11] Add new error - WORKBOOK_IS_ALREADY_RESTORED --- src/components/error-response-presenter.ts | 8 ++++++++ src/const/us-error-constants.ts | 1 + src/services/new/workbook/restore-workbook.ts | 7 ++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/error-response-presenter.ts b/src/components/error-response-presenter.ts index 4c679783..68b23d08 100644 --- a/src/components/error-response-presenter.ts +++ b/src/components/error-response-presenter.ts @@ -454,6 +454,14 @@ export default (error: AppError | DBError) => { }, }; } + case US_ERRORS.WORKBOOK_IS_ALREADY_RESTORED: { + return { + code: 400, + response: { + message: 'The workbook is alredy restored', + }, + }; + } case US_ERRORS.WORKBOOK_ENTITY_ERROR: { return { code: 500, diff --git a/src/const/us-error-constants.ts b/src/const/us-error-constants.ts index 50ebbbe2..0498481d 100644 --- a/src/const/us-error-constants.ts +++ b/src/const/us-error-constants.ts @@ -64,6 +64,7 @@ const US_ERRORS = { COLLECTION_NOT_EXISTS: 'COLLECTION_NOT_EXISTS', COLLECTION_CIRCULAR_REFERENCE_ERROR: 'COLLECTION_CIRCULAR_REFERENCE_ERROR', WORKBOOK_NOT_EXISTS: 'WORKBOOK_NOT_EXISTS', + WORKBOOK_IS_ALREADY_RESTORED: 'WORKBOOK_IS_ALREADY_RESTORED', WORKBOOK_ENTITY_ERROR: 'WORKBOOK_ENTITY_ERROR', WORKBOOK_COPY_FILE_CONNECTION_ERROR: 'WORKBOOK_COPY_FILE_CONNECTION_ERROR', WORKBOOK_OPERATION_FORBIDDEN: 'WORKBOOK_OPERATION_FORBIDDEN', diff --git a/src/services/new/workbook/restore-workbook.ts b/src/services/new/workbook/restore-workbook.ts index 649b3ef1..3cc6d703 100644 --- a/src/services/new/workbook/restore-workbook.ts +++ b/src/services/new/workbook/restore-workbook.ts @@ -48,7 +48,6 @@ export const restoreWorkbook = async ( [WorkbookModelColumn.TenantId]: tenantId, [WorkbookModelColumn.WorkbookId]: workbookId, }) - .andWhereNot(WorkbookModelColumn.DeletedAt, null) .first() .timeout(WorkbookModel.DEFAULT_QUERY_TIMEOUT); @@ -58,6 +57,12 @@ export const restoreWorkbook = async ( }); } + if (model.deletedAt !== null) { + throw new AppError(US_ERRORS.WORKBOOK_IS_ALREADY_RESTORED, { + code: US_ERRORS.WORKBOOK_IS_ALREADY_RESTORED, + }); + } + const entries = await Entry.query(targetTrx) .select() .skipUndefined() From 5d3a0baed789992140e1412a554eef16e374643d Mon Sep 17 00:00:00 2001 From: Sergei Samokhvalov Date: Wed, 6 Dec 2023 17:15:25 +0300 Subject: [PATCH 11/11] Fix after review --- src/services/new/workbook/restore-workbook.ts | 57 ++++++------------- 1 file changed, 17 insertions(+), 40 deletions(-) diff --git a/src/services/new/workbook/restore-workbook.ts b/src/services/new/workbook/restore-workbook.ts index 3cc6d703..109e47b2 100644 --- a/src/services/new/workbook/restore-workbook.ts +++ b/src/services/new/workbook/restore-workbook.ts @@ -41,9 +41,8 @@ export const restoreWorkbook = async ( const targetTrx = getReplica(trx); - const model: Optional = await WorkbookModel.query(targetTrx) + const model = await WorkbookModel.query(targetTrx) .select() - .skipUndefined() .where({ [WorkbookModelColumn.TenantId]: tenantId, [WorkbookModelColumn.WorkbookId]: workbookId, @@ -57,27 +56,16 @@ export const restoreWorkbook = async ( }); } - if (model.deletedAt !== null) { + if (model.deletedAt === null) { throw new AppError(US_ERRORS.WORKBOOK_IS_ALREADY_RESTORED, { code: US_ERRORS.WORKBOOK_IS_ALREADY_RESTORED, }); } - const entries = await Entry.query(targetTrx) - .select() - .skipUndefined() - .where({ - [EntryColumn.WorkbookId]: workbookId, - [EntryColumn.TenantId]: tenantId, - [EntryColumn.IsDeleted]: true, - }) - .timeout(Entry.DEFAULT_QUERY_TIMEOUT); - const primaryTrx = getPrimary(trx); const result = await transaction(primaryTrx, async (transactionTrx) => { const restoredWorkbook = await WorkbookModel.query(transactionTrx) - .skipUndefined() .patch({ [WorkbookModelColumn.DeletedBy]: null, [WorkbookModelColumn.DeletedAt]: null, @@ -91,32 +79,21 @@ export const restoreWorkbook = async ( .first() .timeout(WorkbookModel.DEFAULT_QUERY_TIMEOUT); - await Promise.all( - entries.map(async (entry) => { - const {entryId, displayKey, key} = entry; - - const newInnerMeta = { - ...entry.innerMeta, - oldKey: key as string, - oldDisplayKey: displayKey as string, - }; - - return await Entry.query(transactionTrx) - .patch({ - key: key?.replace(TRASH_FOLDER + '/', ''), - displayKey: displayKey?.replace(TRASH_FOLDER + '/', ''), - innerMeta: newInnerMeta, - isDeleted: false, - deletedAt: null, - updatedAt: raw(CURRENT_TIMESTAMP), - }) - .where({ - entryId, - }) - .andWhere(EntryColumn.DeletedAt, '>=', model.deletedAt) - .timeout(DEFAULT_QUERY_TIMEOUT); - }), - ); + await Entry.query(transactionTrx) + .patch({ + key: raw(`regexp_replace(key, '${TRASH_FOLDER}/', '')`), + displayKey: raw(`regexp_replace(display_key, '${TRASH_FOLDER}/', '')`), + innerMeta: raw(`inner_meta - 'oldKey' - 'oldDisplayKey'`), + isDeleted: false, + deletedAt: null, + updatedAt: raw(CURRENT_TIMESTAMP), + }) + .where({ + [EntryColumn.WorkbookId]: workbookId, + [EntryColumn.TenantId]: tenantId, + }) + .andWhere(EntryColumn.DeletedAt, '>=', model.deletedAt) + .timeout(DEFAULT_QUERY_TIMEOUT); return restoredWorkbook; });