✨Added support of the session editing key for the OnlyOffice connector
This commit is contained in:
committed by
Eric Doughty-Papassideris
parent
b6be90f825
commit
c66123f450
@@ -61,6 +61,8 @@ import archiver from "archiver";
|
||||
import internal from "stream";
|
||||
import config from "config";
|
||||
import { randomUUID } from "crypto";
|
||||
import { MultipartFile } from "@fastify/multipart";
|
||||
import { UploadOptions } from "src/services/files/types";
|
||||
|
||||
export class DocumentsService {
|
||||
version: "1";
|
||||
@@ -1030,13 +1032,15 @@ export class DocumentsService {
|
||||
* 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 file Multipart files from the incoming http request
|
||||
* @param options Optional upload information from the request
|
||||
* @param context
|
||||
*/
|
||||
endEditing = async (
|
||||
editing_session_key: string,
|
||||
newFileUrl: string | null,
|
||||
context: DriveExecutionContext,
|
||||
file: MultipartFile,
|
||||
options: UploadOptions,
|
||||
context: CompanyExecutionContext,
|
||||
) => {
|
||||
if (!context) {
|
||||
this.logger.error("invalid execution context");
|
||||
@@ -1062,19 +1066,21 @@ export class DocumentsService {
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
if (file) {
|
||||
const fileEntity = await globalResolver.services.files.save(null, file, options, context);
|
||||
|
||||
await globalResolver.services.documents.documents.createVersion(
|
||||
driveFile.id,
|
||||
{
|
||||
drive_item_id: driveFile.id,
|
||||
provider: "internal",
|
||||
file_metadata: {
|
||||
external_id: fileEntity.id,
|
||||
source: "internal",
|
||||
},
|
||||
},
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1092,7 +1098,6 @@ export class DocumentsService {
|
||||
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));
|
||||
|
||||
@@ -392,6 +392,7 @@ export class DocumentsController {
|
||||
return await globalResolver.services.documents.documents.endEditing(
|
||||
editing_session_key,
|
||||
null,
|
||||
null,
|
||||
context,
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -406,10 +407,44 @@ export class DocumentsController {
|
||||
endEditing = async (
|
||||
request: FastifyRequest<{
|
||||
Params: ItemRequestByEditingSessionKeyParams;
|
||||
Body: { editorApplicationId: string };
|
||||
Querystring: Record<string, string>;
|
||||
Body: {
|
||||
item: Partial<DriveFile>;
|
||||
version: Partial<FileVersion>;
|
||||
};
|
||||
}>,
|
||||
) => {
|
||||
console.log(request); // make linter happy
|
||||
const { editing_session_key } = request.params;
|
||||
if (!editing_session_key) throw new CrudException("Editing session key must be set", 400);
|
||||
|
||||
const context = getDriveExecutionContext(request);
|
||||
|
||||
if (request.isMultipart()) {
|
||||
const file = await request.file();
|
||||
const q: Record<string, string> = request.query;
|
||||
const options: UploadOptions = {
|
||||
totalChunks: parseInt(q.resumableTotalChunks || q.total_chunks) || 1,
|
||||
totalSize: parseInt(q.resumableTotalSize || q.total_size) || 0,
|
||||
chunkNumber: parseInt(q.resumableChunkNumber || q.chunk_number) || 1,
|
||||
filename: q.resumableFilename || q.filename || file?.filename || undefined,
|
||||
type: q.resumableType || q.type || file?.mimetype || undefined,
|
||||
waitForThumbnail: !!q.thumbnail_sync,
|
||||
ignoreThumbnails: false,
|
||||
};
|
||||
return await globalResolver.services.documents.documents.endEditing(
|
||||
editing_session_key,
|
||||
file,
|
||||
options,
|
||||
context,
|
||||
);
|
||||
} else {
|
||||
return await globalResolver.services.documents.documents.endEditing(
|
||||
editing_session_key,
|
||||
null,
|
||||
null,
|
||||
context,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
downloadGetToken = async (
|
||||
|
||||
@@ -105,7 +105,7 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, _options, next)
|
||||
method: "DELETE",
|
||||
url: `${serviceUrl}/editing_session/:editing_session_key`,
|
||||
preValidation: [fastify.authenticateOptional],
|
||||
handler: documentsController.endEditing.bind(documentsController),
|
||||
handler: documentsController.cancelEditing.bind(documentsController),
|
||||
});
|
||||
|
||||
fastify.route({
|
||||
|
||||
@@ -409,6 +409,36 @@ export default class UserApi {
|
||||
});
|
||||
}
|
||||
|
||||
async endEditingDocument(
|
||||
editingSessionKey: string
|
||||
): Promise<Response> {
|
||||
const fullPath = `${__dirname}/assets/${UserApi.ALL_FILES[0]}`;
|
||||
const readable= Readable.from(fs.createReadStream(fullPath));
|
||||
const form = formAutoContent({ file: readable });
|
||||
form.headers["authorization"] = `Bearer ${this.jwt}`;
|
||||
|
||||
return await this.platform.app.inject({
|
||||
method: "POST",
|
||||
url: `${UserApi.DOC_URL}/companies/${this.platform.workspace.company_id}/item/editing_session/${editingSessionKey}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${this.jwt}`
|
||||
},
|
||||
...form,
|
||||
});
|
||||
}
|
||||
|
||||
async cancelEditingDocument(
|
||||
editingSessionKey: string,
|
||||
): Promise<Response> {
|
||||
return await this.platform.app.inject({
|
||||
method: "DELETE",
|
||||
url: `${UserApi.DOC_URL}/companies/${this.platform.workspace.company_id}/item/editing_session/${editingSessionKey}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${this.jwt}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async beginEditingDocumentExpectOk(
|
||||
driveFileId: string,
|
||||
editorApplicationId: string,
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// import { OidcJwtVerifier } from "../../../src/services/console/clients/remote-jwks-verifier";
|
||||
//
|
||||
// export class UserAuthorization {
|
||||
// /**
|
||||
// * Just send the login requests without any validation and login response assertion
|
||||
// */
|
||||
// public async login(session?: string) {
|
||||
// if (session !== undefined) {
|
||||
// this.session = session;
|
||||
// } else {
|
||||
// this.session = uuidv1();
|
||||
// }
|
||||
// const payload = {
|
||||
// claims: {
|
||||
// sub: this.user.id,
|
||||
// first_name: this.user.first_name,
|
||||
// sid: this.session,
|
||||
// },
|
||||
// };
|
||||
// const verifierMock = jest.spyOn(OidcJwtVerifier.prototype, "verifyIdToken");
|
||||
// verifierMock.mockImplementation(() => {
|
||||
// return Promise.resolve(payload); // Return the predefined payload
|
||||
// });
|
||||
// return await this.api.post("/internal/services/console/v1/login", {
|
||||
// oidc_id_token: "sample_oidc_token",
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// public async logout() {
|
||||
// const payload = {
|
||||
// claims: {
|
||||
// iss: "tdrive_lemonldap",
|
||||
// sub: this.user.id,
|
||||
// sid: this.session,
|
||||
// aud: "your-audience",
|
||||
// iat: Math.floor(Date.now() / 1000),
|
||||
// jti: "jwt-id",
|
||||
// events: {
|
||||
// "http://schemas.openid.net/event/backchannel-logout": {},
|
||||
// },
|
||||
// }
|
||||
// };
|
||||
// const verifierMock = jest.spyOn(OidcJwtVerifier.prototype, "verifyLogoutToken");
|
||||
// verifierMock.mockImplementation(() => {
|
||||
// return Promise.resolve(payload); // Return the predefined payload
|
||||
// });
|
||||
//
|
||||
// return await this.api.post("/internal/services/console/v1/backchannel_logout", {
|
||||
// logout_token: "logout_token_rsa256",
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
@@ -4,6 +4,7 @@ import { init, TestPlatform } from "../setup";
|
||||
import UserApi from "../common/user-api";
|
||||
|
||||
import { DriveFile, TYPE as DriveFileType } from "../../../src/services/documents/entities/drive-file";
|
||||
import exp = require("node:constants");
|
||||
|
||||
describe("the Drive's documents' editing session kind-of-lock", () => {
|
||||
let platform: TestPlatform | null;
|
||||
@@ -110,8 +111,28 @@ describe("the Drive's documents' editing session kind-of-lock", () => {
|
||||
expect(temporaryDocument.id).toBe(foundDocumentResult.json().id);
|
||||
});
|
||||
|
||||
it('can end an editing session on a document only once with the right key', async () => {
|
||||
it('can cancel an editing session on a document only once with the right key', async () => {
|
||||
//given
|
||||
const editingSessionKey = await currentUser.beginEditingDocumentExpectOk(temporaryDocument.id, 'e2e_testing');
|
||||
|
||||
//when
|
||||
const response = await currentUser.cancelEditingDocument(editingSessionKey);
|
||||
//then
|
||||
expect(response.statusCode).toBe(200);
|
||||
const newSessionKey = await currentUser.beginEditingDocumentExpectOk(temporaryDocument.id, 'e2e_testing');
|
||||
expect(newSessionKey).not.toEqual(editingSessionKey);
|
||||
});
|
||||
|
||||
it('can end editing with a new version of document', async () => {
|
||||
//given
|
||||
const editingSessionKey = await currentUser.beginEditingDocumentExpectOk(temporaryDocument.id, 'e2e_testing');
|
||||
//when
|
||||
const response = await currentUser.endEditingDocument(editingSessionKey);
|
||||
|
||||
//then
|
||||
expect(response.statusCode).toBe(200);
|
||||
const document = await currentUser.getDocumentOKCheck(temporaryDocument.id);
|
||||
expect(document.versions.length).toEqual(2);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user