From c0c17993cf1050516ffd20ecf60b44c2f1975cdf Mon Sep 17 00:00:00 2001 From: Alexander Onnikov Date: Fri, 19 Jan 2024 22:27:52 +0700 Subject: [PATCH] UBERF-4905 Change collaborator client payload (#4392) Signed-off-by: Alexander Onnikov --- packages/collaborator-client/src/client.ts | 33 ++++++++++-- packages/collaborator-client/src/index.ts | 1 + packages/collaborator-client/src/utils.ts | 24 +++++++++ packages/presentation/src/collaborator.ts | 6 ++- server/collaborator/src/server.ts | 60 +++++++++++----------- 5 files changed, 86 insertions(+), 38 deletions(-) create mode 100644 packages/collaborator-client/src/utils.ts diff --git a/packages/collaborator-client/src/client.ts b/packages/collaborator-client/src/client.ts index 4d2f7b2391..1448bc11ee 100644 --- a/packages/collaborator-client/src/client.ts +++ b/packages/collaborator-client/src/client.ts @@ -13,7 +13,8 @@ // limitations under the License. // -import { Class, Doc, Markup, Ref, concatLink } from '@hcengineering/core' +import { Class, Doc, Hierarchy, Markup, Ref, concatLink } from '@hcengineering/core' +import { minioDocumentId, mongodbDocumentId } from './utils' /** * @public @@ -26,18 +27,32 @@ export interface CollaboratorClient { /** * @public */ -export function getClient (token: string, collaboratorUrl: string): CollaboratorClient { - return new CollaboratorClientImpl(token, collaboratorUrl) +export function getClient (hierarchy: Hierarchy, token: string, collaboratorUrl: string): CollaboratorClient { + return new CollaboratorClientImpl(hierarchy, token, collaboratorUrl) } class CollaboratorClientImpl implements CollaboratorClient { constructor ( + private readonly hierarchy: Hierarchy, private readonly token: string, private readonly collaboratorUrl: string ) {} + initialContentId (classId: Ref>, docId: Ref, attribute: string): string { + const domain = this.hierarchy.getDomain(classId) + return mongodbDocumentId(domain, docId, attribute) + } + async get (classId: Ref>, docId: Ref, attribute: string): Promise { - const url = concatLink(this.collaboratorUrl, `/api/content/${classId}/${docId}/${attribute}`) + const documentId = encodeURIComponent(minioDocumentId(docId, attribute)) + const initialContentId = encodeURIComponent(this.initialContentId(classId, docId, attribute)) + attribute = encodeURIComponent(attribute) + + const url = concatLink( + this.collaboratorUrl, + `/api/content/${documentId}/${attribute}?initialContentId=${initialContentId}` + ) + const res = await fetch(url, { method: 'GET', headers: { @@ -50,7 +65,15 @@ class CollaboratorClientImpl implements CollaboratorClient { } async update (classId: Ref>, docId: Ref, attribute: string, value: Markup): Promise { - const url = concatLink(this.collaboratorUrl, `/api/content/${classId}/${docId}/${attribute}`) + const documentId = encodeURIComponent(minioDocumentId(docId, attribute)) + const initialContentId = encodeURIComponent(this.initialContentId(classId, docId, attribute)) + attribute = encodeURIComponent(attribute) + + const url = concatLink( + this.collaboratorUrl, + `/api/content/${documentId}/${attribute}?initialContentId=${initialContentId}` + ) + await fetch(url, { method: 'PUT', headers: { diff --git a/packages/collaborator-client/src/index.ts b/packages/collaborator-client/src/index.ts index 14e78297e9..2ac44b392f 100644 --- a/packages/collaborator-client/src/index.ts +++ b/packages/collaborator-client/src/index.ts @@ -14,3 +14,4 @@ // export { type CollaboratorClient, getClient } from './client' +export * from './utils' diff --git a/packages/collaborator-client/src/utils.ts b/packages/collaborator-client/src/utils.ts new file mode 100644 index 0000000000..9ce34ca636 --- /dev/null +++ b/packages/collaborator-client/src/utils.ts @@ -0,0 +1,24 @@ +// +// Copyright © 2024 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { Doc, Domain, Ref } from '@hcengineering/core' + +export function minioDocumentId (docId: Ref, attribute?: string): string { + return attribute !== undefined ? `minio://${docId}%${attribute}` : `minio://${docId}` +} + +export function mongodbDocumentId (domain: Domain, docId: Ref, attribute: string): string { + return `mongodb://${domain}/${docId}/${attribute}` +} diff --git a/packages/presentation/src/collaborator.ts b/packages/presentation/src/collaborator.ts index 9211689f31..600f0c95ae 100644 --- a/packages/presentation/src/collaborator.ts +++ b/packages/presentation/src/collaborator.ts @@ -13,20 +13,22 @@ // limitations under the License. // -import { type CollaboratorClient, getClient } from '@hcengineering/collaborator-client' +import { type CollaboratorClient, getClient as getCollaborator } from '@hcengineering/collaborator-client' import type { Class, Doc, Markup, Ref } from '@hcengineering/core' import { getMetadata } from '@hcengineering/platform' +import { getClient } from '.' import presentation from './plugin' /** * @public */ export function getCollaboratorClient (): CollaboratorClient { + const hierarchy = getClient().getHierarchy() const token = getMetadata(presentation.metadata.Token) ?? '' const collaboratorURL = getMetadata(presentation.metadata.CollaboratorUrl) ?? '' - return getClient(token, collaboratorURL) + return getCollaborator(hierarchy, token, collaboratorURL) } /** diff --git a/server/collaborator/src/server.ts b/server/collaborator/src/server.ts index f809881935..6a50d37424 100644 --- a/server/collaborator/src/server.ts +++ b/server/collaborator/src/server.ts @@ -153,18 +153,18 @@ export async function start ( const restCtx = ctx.newChild('REST', {}) - const getContext = (token: Token, documentId: string): Context => { + const getContext = (token: Token, initialContentId?: string): Context => { return { connectionId: generateId(), workspaceId: token.workspace, clientFactory: getClientFactory(token, controller), - initialContentId: documentId, + initialContentId: initialContentId ?? '', targetContentId: '' } } // eslint-disable-next-line @typescript-eslint/no-misused-promises - app.get('/api/content/:classId/:docId/:attribute', async (req, res) => { + app.get('/api/content/:documentId/:field', async (req, res) => { console.log('handle request', req.method, req.url) const authHeader = req.headers.authorization @@ -176,22 +176,21 @@ export async function start ( const token = authHeader.split(' ')[1] const decodedToken = decodeToken(token) - const docId = req.params.docId - const attribute = req.params.attribute + const documentId = req.params.documentId + const field = req.params.field + const initialContentId = req.query.initialContentId as string - if (docId === undefined || docId === '') { - res.status(400).send({ err: "'docId' is missing" }) + if (documentId === undefined || documentId === '') { + res.status(400).send({ err: "'documentId' is missing" }) return } - if (attribute === undefined || attribute === '') { - res.status(400).send({ err: "'attribute' is missing" }) + if (field === undefined || field === '') { + res.status(400).send({ err: "'field' is missing" }) return } - const documentId = `minio://${docId}%${attribute}` - - const context = getContext(decodedToken, documentId) + const context = getContext(decodedToken, initialContentId) await restCtx.with(`${req.method} /content`, {}, async (ctx) => { const connection = await ctx.with('connect', {}, async () => { @@ -202,7 +201,7 @@ export async function start ( const html = await ctx.with('transform', {}, async () => { let content = '' await connection.transact((document) => { - content = transformer.fromYdoc(document, attribute) + content = transformer.fromYdoc(document, field) }) return content }) @@ -221,7 +220,7 @@ export async function start ( }) // eslint-disable-next-line @typescript-eslint/no-misused-promises - app.put('/api/content/:classId/:docId/:attribute', async (req, res) => { + app.put('/api/content/:documentId/:field', async (req, res) => { console.log('handle request', req.method, req.url) const authHeader = req.headers.authorization @@ -233,27 +232,26 @@ export async function start ( const token = authHeader.split(' ')[1] const decodedToken = decodeToken(token) - const docId = req.params.docId - const attribute = req.params.attribute - - if (docId === undefined || docId === '') { - res.status(400).send({ err: "'docId' is missing" }) - return - } - - if (attribute === undefined || attribute === '') { - res.status(400).send({ err: "'attribute' is missing" }) - return - } - - const documentId = `minio://${docId}%${attribute}` + const documentId = req.params.documentId + const field = req.params.field + const initialContentId = req.query.initialContentId as string const data = req.body.html ?? '

' - const context = getContext(decodedToken, documentId) + if (documentId === undefined || documentId === '') { + res.status(400).send({ err: "'documentId' is missing" }) + return + } + + if (field === undefined || field === '') { + res.status(400).send({ err: "'field' is missing" }) + return + } + + const context = getContext(decodedToken, initialContentId) await restCtx.with(`${req.method} /content`, {}, async (ctx) => { const update = await ctx.with('transform', {}, () => { - const ydoc = transformer.toYdoc(data, attribute) + const ydoc = transformer.toYdoc(data, field) return encodeStateAsUpdate(ydoc) }) @@ -264,7 +262,7 @@ export async function start ( try { await ctx.with('update', {}, async () => { await connection.transact((document) => { - const fragment = document.getXmlFragment(attribute) + const fragment = document.getXmlFragment(field) document.transact((tr) => { fragment.delete(0, fragment.length) applyUpdate(document, update)