mirror of
https://github.com/suitenumerique/docs.git
synced 2026-08-31 20:29:52 +02:00
✨(collaboration) add a get-ydoc endpoint on yhub
`GET /collaboration/get-ydoc/v1/docs/{id}` answers the current Yjs state of a
document as a raw binary update, the read counterpart of create-ydoc, and
204 when the document has no content yet
This commit is contained in:
@@ -87,6 +87,7 @@ and this project adheres to
|
||||
`503` instead of denying access like a permission failure, so clients retry
|
||||
instead of giving up. The built-in endpoints can also answer JSON on
|
||||
`Accept: application/json`
|
||||
- ✨(collaboration) add a get-ydoc endpoint on yhub
|
||||
- ✨(backend) call YHubService to seed initial document content
|
||||
- ✨(backend) reset the yhub connections of a document and its descendants
|
||||
when an access or the link configuration changes
|
||||
|
||||
@@ -190,6 +190,19 @@ class YHubService:
|
||||
|
||||
return response
|
||||
|
||||
def get_ydoc(self, document):
|
||||
"""
|
||||
Return the current Yjs state of a document, None when it has none.
|
||||
|
||||
The raw update is what `create_ydoc` takes, so the state of a document
|
||||
can be copied into another one. The built-in `ydoc` endpoint is not
|
||||
used, it answers the lib0 encoding of an envelope rather than the
|
||||
update itself.
|
||||
"""
|
||||
response = self.request("get", self.build_url("get-ydoc", document))
|
||||
|
||||
return response.content or None
|
||||
|
||||
def create_ydoc(self, document, update):
|
||||
"""
|
||||
Seed the initial Yjs state of a document.
|
||||
|
||||
@@ -222,3 +222,29 @@ def test_reset_connections_of_a_single_user(mock_request):
|
||||
|
||||
_args, kwargs = mock_request.call_args
|
||||
assert kwargs["headers"]["X-User-Id"] == str(user.pk)
|
||||
|
||||
|
||||
@patch("requests.request")
|
||||
def test_get_ydoc(mock_request):
|
||||
"""Should return the raw update the collaboration server holds."""
|
||||
mock_request.return_value.ok = True
|
||||
mock_request.return_value.content = b"\x01\x02raw yjs update"
|
||||
|
||||
update = YHubService().get_ydoc(DOCUMENT)
|
||||
|
||||
assert update == b"\x01\x02raw yjs update"
|
||||
args, _kwargs = mock_request.call_args
|
||||
assert args == (
|
||||
"get",
|
||||
f"http://yhub:3002/collaboration/get-ydoc/v1/docs/{DOCUMENT.id!s}",
|
||||
)
|
||||
|
||||
|
||||
@patch("requests.request")
|
||||
def test_get_ydoc_without_content(mock_request):
|
||||
"""A document the collaboration server holds no content for should return None."""
|
||||
mock_request.return_value.ok = True
|
||||
# yhub answers 204 No Content, hence an empty body
|
||||
mock_request.return_value.content = b""
|
||||
|
||||
assert YHubService().get_ydoc(DOCUMENT) is None
|
||||
|
||||
@@ -340,6 +340,37 @@ const api = [
|
||||
},
|
||||
},
|
||||
}),
|
||||
// GET /collaboration/get-ydoc/v1/{org}/{docid} — the current state of a
|
||||
// document as a RAW binary update (`Y.encodeStateAsUpdate` output), the read
|
||||
// counterpart of create-ydoc: yhub's built-in `GET ydoc` answers a lib0-any
|
||||
// encoded `{ doc, awareness }` envelope Django cannot decode. Answers 204
|
||||
// when the room holds no content. Default access purpose: guarded like the
|
||||
// built-in ydoc routes (read access on the doc — the admin JWT, or a user
|
||||
// session able to retrieve it).
|
||||
createApiEndpoint('get-ydoc', {
|
||||
get: {
|
||||
handler: async (req) => {
|
||||
if (req.org !== ORG) {
|
||||
return jsonResponse(400, { error: 'Unknown org' });
|
||||
}
|
||||
if (!UUID4.test(req.docid)) {
|
||||
return jsonResponse(400, { error: 'Room name is invalid' });
|
||||
}
|
||||
const { gcDoc } = await req.yhub.getDoc(
|
||||
req.room,
|
||||
{ gc: true, nongc: false },
|
||||
{ gcOnMerge: false },
|
||||
);
|
||||
// <= 3 bytes is yhub's "no effective content" convention (an empty
|
||||
// update encodes to 2 bytes) — nothing to copy. `null` answers 204.
|
||||
if (gcDoc == null || gcDoc.byteLength <= 3) {
|
||||
return null;
|
||||
}
|
||||
// a Uint8Array is served as application/octet-stream, untouched
|
||||
return gcDoc;
|
||||
},
|
||||
},
|
||||
}),
|
||||
// POST /collaboration/create-ydoc/v1/{org}/{docid} — create a document's
|
||||
// initial Yjs state from a RAW binary update (`Y.encodeStateAsUpdate` /
|
||||
// pycrdt `get_update()` output) posted as application/octet-stream. Unlike
|
||||
|
||||
Reference in New Issue
Block a user