🚧 WIP: backend: beginnings of a transactional editing for plugins (#525)

This commit is contained in:
Eric Doughty-Papassideris
2024-08-25 22:26:27 +02:00
parent 8f5061e613
commit 688217dfdb
4 changed files with 130 additions and 1 deletions
@@ -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,
@@ -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;
@@ -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`,
@@ -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();
}