diff --git a/backend/app/routers/document.py b/backend/app/routers/document.py index 9a851f0..e030f0c 100644 --- a/backend/app/routers/document.py +++ b/backend/app/routers/document.py @@ -203,6 +203,25 @@ def download_doc( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) +@router.get( + "/{doc_id}/download_original", + response_class=StreamingResponse, + responses={ + 200: { + "description": "Successful Response", + "content": {"application/octet-stream": {"schema": {"type": "string"}}}, + } + }, +) +def download_original_doc( + doc_id: int, service: Annotated[DocumentService, Depends(get_service)] +): + try: + return service.download_original_document(doc_id) + except EntityNotFound as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) + + @router.put("/{doc_id}") def update_document( doc_id: int, diff --git a/backend/app/services/document_service.py b/backend/app/services/document_service.py index 2f233e8..a2339e7 100644 --- a/backend/app/services/document_service.py +++ b/backend/app/services/document_service.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from datetime import datetime, timedelta +from io import BytesIO from fastapi import UploadFile from fastapi.responses import StreamingResponse @@ -246,6 +247,41 @@ def download_document(self, doc_id: int) -> StreamingResponse: raise EntityNotFound("Unknown document type") + def download_original_document(self, doc_id: int) -> StreamingResponse: + """ + Download original document. + + Args: + doc_id: Document ID + + Returns: + StreamingResponse with document file + + Raises: + EntityNotFound: If document not found or file not available + """ + doc = self._get_document_by_id(doc_id) + if doc.type == DocumentType.xliff: + if not doc.xliff: + raise EntityNotFound("No XLIFF file found") + original_document = doc.xliff.original_document.encode("utf-8") + output = BytesIO(original_document) + elif doc.type == DocumentType.txt: + if not doc.txt: + raise EntityNotFound("No TXT file found") + original_document = doc.txt.original_document + output = BytesIO(original_document.encode()) + else: + raise EntityNotFound("Unknown document type") + + return StreamingResponse( + output, + media_type="application/octet-stream", + headers={ + "Content-Disposition": f'attachment; filename="{self.encode_to_latin_1(doc.name)}"' + }, + ) + def get_document_records( self, doc_id: int, diff --git a/backend/tests/routers/test_routes_documents.py b/backend/tests/routers/test_routes_documents.py index eafdad9..f0897eb 100644 --- a/backend/tests/routers/test_routes_documents.py +++ b/backend/tests/routers/test_routes_documents.py @@ -1233,3 +1233,35 @@ def test_update_document_to_same_project( updated_doc = s.query(Document).filter_by(id=doc.id).first() assert updated_doc is not None assert updated_doc.project_id == project_id + + +def test_download_original_xliff_doc(user_logged_client: TestClient, session: Session): + """Test downloading original XLIFF document.""" + with open("tests/fixtures/small.xliff", "rb") as fp: + user_logged_client.post("/document/", files={"file": fp}) + + response = user_logged_client.get("/document/1/download_original") + assert response.status_code == 200 + + data = response.read().decode("utf-8") + assert data.startswith(" { return getApiBase() + `/document/${doc_id}/download` } +export const getDownloadOriginalDocLink = (doc_id: number): string => { + return getApiBase() + `/document/${doc_id}/download_original` +} diff --git a/frontend/src/components/DocSegment.vue b/frontend/src/components/DocSegment.vue index 3c19e96..d1afd69 100644 --- a/frontend/src/components/DocSegment.vue +++ b/frontend/src/components/DocSegment.vue @@ -123,26 +123,23 @@ const showHistory = () => {