(backend) serve documents/{id}/formatted-content/ from yhub

The formatted-content endpoint was using the `document.content` to fetch
the ydoc from s3, we want to move from this usage to using yhub to
retrieve the content, so yhub is becoming our source of thruth.
This commit is contained in:
Manuel Raynaud
2026-09-04 15:44:40 +02:00
committed by Anthony LC
parent 03136fe8da
commit c6a10ec137
3 changed files with 77 additions and 5 deletions
+1
View File
@@ -20,6 +20,7 @@ and this project adheres to
- ✨(backend) call YHubService to seed initial document content
- ✨(collaboration) add a get-ydoc endpoint on yhub
- ✨(backend) duplicate a document through the collaboration server
- ✨(backend) serve `documents/{id}/formatted-content/` from yhub
### Changed
+13 -5
View File
@@ -2,7 +2,6 @@
# pylint: disable=too-many-lines
import base64
import ipaddress
import json
import logging
@@ -2437,15 +2436,24 @@ class DocumentViewSet(
"Invalid format. Must be one of: json, markdown, html"
)
# Get the base64 content from the document
# Get the content from the collaboration server, it is the source of
# truth for it
try:
update = YHubService(user=request.user).get_ydoc(document)
except YHubError as e:
logger.error("Error getting content for document %s: %s", pk, e)
return drf_response.Response(
{"error": "Failed to get document content"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
content = None
base64_content = document.content
if base64_content is not None:
if update is not None:
# Convert using the y-provider service
try:
yprovider = Converter()
result = yprovider.convert(
base64.b64decode(base64_content),
update,
mime_types.YJS,
{
"markdown": mime_types.MARKDOWN,
@@ -11,10 +11,30 @@ from rest_framework import status
from rest_framework.test import APIClient
from core import factories
from core.services.yhub_services import (
ServiceUnavailableError as YHubServiceUnavailableError,
)
pytestmark = pytest.mark.django_db
@pytest.fixture(autouse=True, name="mock_yhub")
def mock_yhub_fixture():
"""
The content of a document is held by the collaboration server.
It stands for a server holding the very content the factories gave the
documents, which is what an editor connected to it would have saved.
"""
def get_ydoc(document):
return base64.b64decode(document.content) if document.content else None
with patch("core.api.viewsets.YHubService") as mock_service:
mock_service.return_value.get_ydoc.side_effect = get_ydoc
yield mock_service
@pytest.mark.parametrize(
"reach, role",
[
@@ -182,3 +202,46 @@ def test_api_documents_formatted_content_empty_document(mock_request):
assert data["title"] == document.title
assert data["content"] is None
mock_request.assert_not_called()
@patch("core.services.converter_services.YdocConverter.convert")
def test_api_documents_formatted_content_from_collaboration_server(
mock_content, mock_yhub
):
"""The content converted is the one held by the collaboration server."""
document = factories.DocumentFactory(link_reach="public")
mock_content.return_value = {"some": "data"}
# what the collaboration server holds, edited since Django last saw it
mock_yhub.return_value.get_ydoc.side_effect = None
mock_yhub.return_value.get_ydoc.return_value = b"\x01\x02edited update"
response = APIClient().get(
f"/api/v1.0/documents/{document.id!s}/formatted-content/"
)
assert response.status_code == status.HTTP_200_OK
mock_yhub.return_value.get_ydoc.assert_called_once_with(document)
mock_content.assert_called_once_with(
b"\x01\x02edited update",
"application/vnd.yjs.doc",
"application/json",
)
@patch("core.services.converter_services.YdocConverter.convert")
def test_api_documents_formatted_content_collaboration_server_error(
mock_content, mock_yhub
):
"""A content the collaboration server cannot serve should answer a 500."""
document = factories.DocumentFactory(link_reach="public")
mock_yhub.return_value.get_ydoc.side_effect = YHubServiceUnavailableError(
"Failed to connect to the yhub service"
)
response = APIClient().get(
f"/api/v1.0/documents/{document.id!s}/formatted-content/"
)
assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
assert response.json() == {"error": "Failed to get document content"}
mock_content.assert_not_called()