From 688217dfdbb8ab550ba2154372f04f14f5e391e7 Mon Sep 17 00:00:00 2001 From: Eric Doughty-Papassideris Date: Sun, 25 Aug 2024 22:26:27 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20WIP:=20backend:=20beginnings=20o?= =?UTF-8?q?f=20a=20transactional=20editing=20for=20plugins=20(#525)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/documents/services/index.ts | 74 +++++++++++++++++++ .../documents/web/controllers/documents.ts | 38 ++++++++++ .../node/src/services/documents/web/routes.ts | 14 ++++ .../src/services/onlyoffice.service.ts | 5 +- 4 files changed, 130 insertions(+), 1 deletion(-) diff --git a/tdrive/backend/node/src/services/documents/services/index.ts b/tdrive/backend/node/src/services/documents/services/index.ts index 25d1a8c4..b8c4ae54 100644 --- a/tdrive/backend/node/src/services/documents/services/index.ts +++ b/tdrive/backend/node/src/services/documents/services/index.ts @@ -1025,6 +1025,80 @@ export class DocumentsService { CrudException.throwMe(error, new CrudException("Failed to begin editing Drive item", 500)); } }; + + /** + * End editing session either by providing a URL to a new file to create a version, + * or not, to just cancel the session. + * @param editing_session_key Editing key of the DriveFile + * @param newFileUrl Optional URL to a new version of the file to store + * @param options Optional upload information from the request + */ + endEditing = async ( + editing_session_key: string, + newFileUrl: string | null, + context: DriveExecutionContext, + ) => { + if (!context) { + this.logger.error("invalid execution context"); + return null; + } + if (!editing_session_key) { + this.logger.error("Invalid editing_session_key: " + JSON.stringify(editing_session_key)); + throw new CrudException("Invalid editing_session_key", 400); + } + + const driveFile = await this.repository.findOne({ editing_session_key }, {}, context); + if (!driveFile) { + this.logger.error("Drive item not found by editing session key"); + throw new CrudException("Item not found by editing session key", 404); + } + + const hasAccess = await checkAccess(driveFile.id, driveFile, "write", this.repository, context); + if (!hasAccess) { + logger.error("user does not have access drive item " + driveFile.id); + CrudException.throwMe( + new Error("user does not have access to the drive item"), + new CrudException("user does not have access drive item", 401), + ); + } + + if (newFileUrl) { + // const response = await axios.request({ + // url: domain + req.url, + // method: req.method as any, + // headers: _.omit(req.headers, "host", "content-length") as { + // [key: string]: string; + // }, + // data: req.body as any, + // responseType: "stream", + // maxRedirects: 0, + // validateStatus: null, + // }); + //TODO: save file into new version + } + + try { + const result = await this.repository.atomicCompareAndSet( + driveFile, + "editing_session_key", + editing_session_key, + null, + ); + if (!result.didSet) + throw new Error( + `Couldn't set editing_session_key ${JSON.stringify( + editing_session_key, + )} on DriveFile ${JSON.stringify(driveFile.id)} because it is ${JSON.stringify( + result.currentValue, + )}`, + ); + return { ok: true }; + } catch (error) { + logger.error({ error: `${error}` }, "Failed to cancel editing Drive item"); + CrudException.throwMe(error, new CrudException("Failed to cancel editing Drive item", 500)); + } + }; + downloadGetToken = async ( ids: string[], versionId: string | null, diff --git a/tdrive/backend/node/src/services/documents/web/controllers/documents.ts b/tdrive/backend/node/src/services/documents/web/controllers/documents.ts index 973c0765..55341788 100644 --- a/tdrive/backend/node/src/services/documents/web/controllers/documents.ts +++ b/tdrive/backend/node/src/services/documents/web/controllers/documents.ts @@ -372,6 +372,44 @@ export class DocumentsController { } }; + /** + * Finish an editing session by cancelling it. + */ + cancelEditing = async ( + request: FastifyRequest<{ + Params: ItemRequestByEditingSessionKeyParams; + Body: { editorApplicationId: string }; + }>, + ) => { + try { + const context = getDriveExecutionContext(request); + const { editing_session_key } = request.params; + + if (!editing_session_key) throw new CrudException("Missing editing_session_key", 400); + + return await globalResolver.services.documents.documents.endEditing( + editing_session_key, + null, + context, + ); + } catch (error) { + logger.error({ error: `${error}` }, "Failed to begin editing Drive item"); + CrudException.throwMe(error, new CrudException("Failed to begin editing Drive item", 500)); + } + }; + //TODO: will need a save under session key, but without ending the edit (for force saves) + /** + * Finish an editing session for a given `editing_session_key` by uploading the new version of the File + */ + endEditing = async ( + request: FastifyRequest<{ + Params: ItemRequestByEditingSessionKeyParams; + Body: { editorApplicationId: string }; + }>, + ) => { + console.log(request); // make linter happy + }; + downloadGetToken = async ( request: FastifyRequest<{ Params: ItemRequestParams; diff --git a/tdrive/backend/node/src/services/documents/web/routes.ts b/tdrive/backend/node/src/services/documents/web/routes.ts index ed1e3e5d..be75de4d 100644 --- a/tdrive/backend/node/src/services/documents/web/routes.ts +++ b/tdrive/backend/node/src/services/documents/web/routes.ts @@ -94,6 +94,20 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, _options, next) handler: documentsController.getByEditingSessionKey.bind(documentsController), }); + fastify.route({ + method: "POST", + url: `${serviceUrl}/editing_session/:editing_session_key`, + preValidation: [fastify.authenticateOptional], + handler: documentsController.endEditing.bind(documentsController), + }); + + fastify.route({ + method: "DELETE", + url: `${serviceUrl}/editing_session/:editing_session_key`, + preValidation: [fastify.authenticateOptional], + handler: documentsController.endEditing.bind(documentsController), + }); + fastify.route({ method: "GET", url: `${serviceUrl}/download/token`, diff --git a/tdrive/connectors/onlyoffice-connector/src/services/onlyoffice.service.ts b/tdrive/connectors/onlyoffice-connector/src/services/onlyoffice.service.ts index 72a9a161..944c3796 100644 --- a/tdrive/connectors/onlyoffice-connector/src/services/onlyoffice.service.ts +++ b/tdrive/connectors/onlyoffice-connector/src/services/onlyoffice.service.ts @@ -171,7 +171,10 @@ class OnlyOfficeService { constructor() { this.poller = new PolledThingieValue('Connect to Only Office', () => this.getVersion(), 10 * 1000 * 60); } - /** Get the latest Only Office version */ + /** Get the latest Only Office version from polling. If the return is `undefined` + * it probably means there is a connection issue contacting the OnlyOffice server + * from the connector. + */ public getLatestVersion() { return this.poller.latest(); }