Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add private route for restore workbook #31

Merged
merged 13 commits into from
Dec 7, 2023
8 changes: 8 additions & 0 deletions src/components/error-response-presenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/const/us-error-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
19 changes: 19 additions & 0 deletions src/controllers/workbooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
getAllWorkbooks,
OrderField,
OrderDirection,
restoreWorkbook,
} from '../services/new/workbook';
import {
formatWorkbookModel,
Expand All @@ -25,6 +26,7 @@ import {
formatWorkbookModelWithOperation,
formatWorkbooksList,
formatSetWorkbookIsTemplate,
formatRestoreWorkbook,
} from '../services/new/workbook/formatters';

export default {
Expand Down Expand Up @@ -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);
},
};
8 changes: 8 additions & 0 deletions src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {WorkbookModel} from '../../../../db/models/new/workbook';

export const formatRestoreWorkbook = (workbookModel: WorkbookModel) => {
return {
workbookId: workbookModel.workbookId,
};
};
1 change: 1 addition & 0 deletions src/services/new/workbook/formatters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
1 change: 1 addition & 0 deletions src/services/new/workbook/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
112 changes: 112 additions & 0 deletions src/services/new/workbook/restore-workbook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
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 = await WorkbookModel.query(targetTrx)
.select()
.where({
[WorkbookModelColumn.TenantId]: tenantId,
[WorkbookModelColumn.WorkbookId]: workbookId,
})
.first()
.timeout(WorkbookModel.DEFAULT_QUERY_TIMEOUT);

if (!model) {
throw new AppError(US_ERRORS.WORKBOOK_NOT_EXISTS, {
code: US_ERRORS.WORKBOOK_NOT_EXISTS,
});
}

if (model.deletedAt === null) {
throw new AppError(US_ERRORS.WORKBOOK_IS_ALREADY_RESTORED, {
code: US_ERRORS.WORKBOOK_IS_ALREADY_RESTORED,
});
}

const primaryTrx = getPrimary(trx);

const result = await transaction(primaryTrx, async (transactionTrx) => {
const restoredWorkbook = await WorkbookModel.query(transactionTrx)
.patch({
[WorkbookModelColumn.DeletedBy]: null,
[WorkbookModelColumn.DeletedAt]: null,
[WorkbookModelColumn.UpdatedAt]: raw(CURRENT_TIMESTAMP),
})
.where({
[WorkbookModelColumn.WorkbookId]: model.workbookId,
[WorkbookModelColumn.TenantId]: tenantId,
})
.returning('*')
.first()
.timeout(WorkbookModel.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;
});

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;
};
81 changes: 81 additions & 0 deletions src/tests/int/common/workbooks.private.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,84 @@ describe('Private Entries in workboooks managment', () => {
await withScopeHeaders(request(app).delete(`/v2/workbooks/${testWorkbookId}`)).expect(200);
});
});

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);

await request(app).post(`/private/v2/workbooks/${testWorkbookId}/restore`).expect(403);

const response = await withScopeHeaders(
request(app)
.post(`/private/v2/workbooks/${testWorkbookId}/restore`)
.set({[US_MASTER_TOKEN_HEADER]: usApp.config.masterToken}),
).expect(200);

const {body} = response;

expect(body).toStrictEqual({
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);
});
});
Loading