From ed36ced9947e573238f8ef5e52c0588059e69069 Mon Sep 17 00:00:00 2001 From: hyungi Date: Fri, 5 Sep 2025 13:00:25 +0900 Subject: [PATCH] =?UTF-8?q?Fix:=20PDF=20=EC=A0=84=EC=9A=A9=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=20=EB=B7=B0=EC=96=B4=20=EC=B2=98=EB=A6=AC=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HTML이 없고 PDF만 있는 문서 클릭 시 PDF 매니저로 자동 리다이렉트 - 뷰어에서 HTML 없는 문서의 경우 PDF 뷰어로 자동 전환 - 문서 관리 페이지에서 PDF 전용 문서 처리 로직 개선 - IndentationError 수정으로 백엔드 안정성 향상 --- backend/src/api/routes/document_links.py | 5 + backend/src/api/routes/highlights.py | 93 ++++++++++++------- frontend/static/js/main.js | 10 ++ .../static/js/viewer/core/document-loader.js | 46 ++++++++- 4 files changed, 119 insertions(+), 35 deletions(-) diff --git a/backend/src/api/routes/document_links.py b/backend/src/api/routes/document_links.py index e4c75a4..4e2fd0e 100644 --- a/backend/src/api/routes/document_links.py +++ b/backend/src/api/routes/document_links.py @@ -581,6 +581,7 @@ async def get_document_backlinks( # 2. 노트에서 오는 백링크 (NoteLink) - 동기 쿼리 사용 try: + print(f"🔍 노트 백링크 조회 시작...") from ...core.database import get_sync_db sync_db = next(get_sync_db()) @@ -626,8 +627,12 @@ async def get_document_backlinks( )) sync_db.close() + print(f"✅ 노트 백링크 조회 완료") except Exception as e: print(f"❌ 노트 백링크 조회 실패: {e}") + print(f"❌ 오류 타입: {type(e).__name__}") + import traceback + print(f"❌ 스택 트레이스: {traceback.format_exc()}") print(f"✅ 총 {len(backlinks)}개의 백링크 반환 (문서 + 노트)") return backlinks diff --git a/backend/src/api/routes/highlights.py b/backend/src/api/routes/highlights.py index 9c66d0b..667f1fe 100644 --- a/backend/src/api/routes/highlights.py +++ b/backend/src/api/routes/highlights.py @@ -434,38 +434,65 @@ async def list_user_highlights( db: AsyncSession = Depends(get_db) ): """사용자의 모든 하이라이트 조회""" - query = select(Highlight).options(selectinload(Highlight.user)).where( - Highlight.user_id == current_user.id - ) - - if document_id: - query = query.where(Highlight.document_id == document_id) - - query = query.order_by(Highlight.created_at.desc()).offset(skip).limit(limit) - - result = await db.execute(query) - highlights = result.scalars().all() - - # 응답 데이터 변환 - response_data = [] - for highlight in highlights: - highlight_data = HighlightResponse( - id=str(highlight.id), - user_id=str(highlight.user_id), - document_id=str(highlight.document_id), - start_offset=highlight.start_offset, - end_offset=highlight.end_offset, - selected_text=highlight.selected_text, - element_selector=highlight.element_selector, - start_container_xpath=highlight.start_container_xpath, - end_container_xpath=highlight.end_container_xpath, - highlight_color=highlight.highlight_color, - highlight_type=highlight.highlight_type, - created_at=highlight.created_at, - updated_at=highlight.updated_at, - note=None + try: + print(f"🔍 하이라이트 조회 시작 - 사용자: {current_user.email}, document_id: {document_id}") + + # 문서 권한이 있는 하이라이트만 조회하도록 Document와 조인 + query = select(Highlight).options(selectinload(Highlight.user)).join( + Document, Highlight.document_id == Document.id + ).where( + and_( + Highlight.user_id == current_user.id, + # 문서 권한 체크: 관리자이거나 문서 관리 권한이 있거나 공개 문서이거나 본인이 업로드한 문서 + or_( + current_user.is_admin == True, + current_user.can_manage_books == True, + Document.is_public == True, + Document.uploaded_by == current_user.id + ) + ) ) - # 메모는 별도 API에서 조회하므로 여기서는 처리하지 않음 - response_data.append(highlight_data) + + if document_id: + query = query.where(Highlight.document_id == document_id) + + query = query.order_by(Highlight.created_at.desc()).offset(skip).limit(limit) + + result = await db.execute(query) + highlights = result.scalars().all() + + print(f"✅ 하이라이트 조회 완료: {len(highlights)}개") + + # 응답 데이터 변환 + response_data = [] + for highlight in highlights: + highlight_data = HighlightResponse( + id=str(highlight.id), + user_id=str(highlight.user_id), + document_id=str(highlight.document_id), + start_offset=highlight.start_offset, + end_offset=highlight.end_offset, + selected_text=highlight.selected_text, + element_selector=highlight.element_selector, + start_container_xpath=highlight.start_container_xpath, + end_container_xpath=highlight.end_container_xpath, + highlight_color=highlight.highlight_color, + highlight_type=highlight.highlight_type, + created_at=highlight.created_at, + updated_at=highlight.updated_at, + note=None + ) + # 메모는 별도 API에서 조회하므로 여기서는 처리하지 않음 + response_data.append(highlight_data) - return response_data + return response_data + + except Exception as e: + print(f"❌ 하이라이트 조회 실패: {e}") + print(f"❌ 오류 타입: {type(e).__name__}") + import traceback + print(f"❌ 스택 트레이스: {traceback.format_exc()}") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Internal server error: {str(e)}" + ) diff --git a/frontend/static/js/main.js b/frontend/static/js/main.js index 4b0b5f7..b2ef76d 100644 --- a/frontend/static/js/main.js +++ b/frontend/static/js/main.js @@ -341,6 +341,16 @@ window.documentApp = () => ({ // 문서 보기 viewDocument(documentId) { + // 문서 정보 찾기 + const document = this.documents.find(doc => doc.id === documentId); + + // HTML이 없고 PDF만 있는 경우 PDF 매니저로 리다이렉트 + if (document && !document.html_path && document.pdf_path) { + console.log('🔄 PDF 전용 문서 - PDF 매니저로 리다이렉트'); + window.location.href = `/pdf-manager.html`; + return; + } + // 현재 페이지 정보를 세션 스토리지에 저장 const currentPage = window.location.pathname.split('/').pop() || 'index.html'; sessionStorage.setItem('previousPage', currentPage); diff --git a/frontend/static/js/viewer/core/document-loader.js b/frontend/static/js/viewer/core/document-loader.js index e3943a3..c3f32d1 100644 --- a/frontend/static/js/viewer/core/document-loader.js +++ b/frontend/static/js/viewer/core/document-loader.js @@ -55,8 +55,8 @@ class DocumentLoader { // 페이지 제목 업데이트 document.title = `${docData.title} - Document Server`; - // PDF 문서가 아닌 경우에만 HTML 로드 - if (!docData.pdf_path && docData.html_path) { + // HTML 경로가 있는 경우에만 HTML 로드 + if (docData.html_path) { // HTML 파일 경로 구성 (백엔드 서버를 통해 접근) const htmlPath = docData.html_path; const fileName = htmlPath.split('/').pop(); @@ -71,6 +71,25 @@ class DocumentLoader { // 문서 내 스크립트 오류 방지를 위한 전역 함수들 정의 this.setupDocumentScriptHandlers(); + } else if (docData.pdf_path) { + // HTML이 없고 PDF만 있는 경우 PDF 뷰어로 자동 전환 + console.log('🔄 HTML이 없는 문서 - PDF 뷰어로 자동 전환'); + const pdfUrl = `/api/documents/${documentId}/pdf?_token=${this.api.getToken()}`; + + // HTML 컨테이너 숨기기 + const htmlContainer = document.getElementById('document-content'); + if (htmlContainer) { + htmlContainer.style.display = 'none'; + } + + // PDF iframe 표시 + const iframe = document.getElementById('pdf-iframe'); + if (iframe) { + iframe.src = pdfUrl; + iframe.style.display = 'block'; + iframe.style.width = '100%'; + iframe.style.height = '100vh'; + } } console.log('✅ 문서 로드 완료:', docData.title, docData.pdf_path ? '(PDF)' : '(HTML)'); @@ -79,6 +98,29 @@ class DocumentLoader { } catch (error) { console.error('Document load error:', error); + // HTML 로드 실패 시 PDF로 자동 전환 시도 + if (docData && docData.pdf_path && !docData.html_path) { + console.log('🔄 HTML이 없는 문서 - PDF 뷰어로 자동 전환'); + // PDF 뷰어로 리다이렉트 + const currentUrl = new URL(window.location); + const pdfUrl = `/api/documents/${documentId}/pdf?_token=${this.api.getToken()}`; + + // iframe에서 PDF 직접 로드 + const iframe = document.getElementById('pdf-iframe'); + if (iframe) { + iframe.src = pdfUrl; + iframe.style.display = 'block'; + } + + // HTML 컨테이너 숨기기 + const htmlContainer = document.getElementById('document-content'); + if (htmlContainer) { + htmlContainer.style.display = 'none'; + } + + return docData; + } + // 백엔드 연결 실패시 목업 데이터로 폴백 console.warn('Using fallback mock data'); const mockDocument = {