feat: 노트북-서적 간 양방향 링크/백링크 시스템 완성
✨ 주요 기능 - 노트 ↔ 서적 문서 간 양방향 링크 생성 및 이동 - 링크 대상 타입 선택 UI (서적 문서/노트북 노트) - 통합 백링크 시스템 (일반 문서에서 노트 백링크도 표시) - 링크 목록 UI 개선 (상세 정보 표시, 타입 구분) 🔧 백엔드 개선 - NoteLink 모델 및 API 추가 (/note-documents/{id}/links, /note-documents/{id}/backlinks) - 일반 문서 백링크 API에서 노트 링크도 함께 조회 - target_content_type, source_content_type 필드 추가 - 노트 문서 콘텐츠 API 추가 (/note-documents/{id}/content) 🎨 프론트엔드 개선 - text-selector.html에서 노트 문서 지원 - 링크 이동 시 contentType에 따른 올바른 URL 생성 - URL 파라미터 파싱 수정 (contentType 지원) - 링크 타입 자동 추론 로직 - 링크 목록 UI 대폭 개선 (출발점/도착점 텍스트, 타입 배지 등) 🐛 버그 수정 - 서적 목록 로드 실패 문제 해결 - 노트에서 링크 생성 시 대상 문서 열기 문제 해결 - 더미 문서로 이동하는 문제 해결 - 캐시 관련 문제 해결
This commit is contained in:
@@ -419,6 +419,7 @@ class BacklinkResponse(BaseModel):
|
||||
source_document_id: str
|
||||
source_document_title: str
|
||||
source_document_book_id: Optional[str]
|
||||
source_content_type: Optional[str] = "document" # "document" or "note"
|
||||
target_document_id: str
|
||||
target_document_title: str
|
||||
selected_text: str # 소스 문서에서 선택한 텍스트
|
||||
@@ -467,8 +468,12 @@ async def get_document_backlinks(
|
||||
|
||||
# 이 문서를 대상으로 하는 모든 링크 조회 (백링크)
|
||||
from ...models import Book
|
||||
from ...models.note_link import NoteLink
|
||||
from ...models.note_document import NoteDocument
|
||||
from ...models.notebook import Notebook
|
||||
|
||||
query = select(DocumentLink, Document, Book).join(
|
||||
# 1. 일반 문서에서 오는 백링크 (DocumentLink)
|
||||
doc_query = select(DocumentLink, Document, Book).join(
|
||||
Document, DocumentLink.source_document_id == Document.id
|
||||
).outerjoin(Book, Document.book_id == Book.id).where(
|
||||
and_(
|
||||
@@ -478,12 +483,13 @@ async def get_document_backlinks(
|
||||
)
|
||||
).order_by(DocumentLink.created_at.desc())
|
||||
|
||||
result = await db.execute(query)
|
||||
doc_result = await db.execute(doc_query)
|
||||
backlinks = []
|
||||
|
||||
print(f"🔍 백링크 쿼리 실행 완료")
|
||||
print(f"🔍 문서 백링크 쿼리 실행 완료")
|
||||
|
||||
for link, source_doc, book in result.fetchall():
|
||||
# 일반 문서 백링크 처리
|
||||
for link, source_doc, book in doc_result.fetchall():
|
||||
print(f"📋 백링크 발견: {source_doc.title} -> {document.title}")
|
||||
print(f" - 소스 텍스트 (selected_text): {link.selected_text}")
|
||||
print(f" - 타겟 텍스트 (target_text): {link.target_text}")
|
||||
@@ -495,6 +501,7 @@ async def get_document_backlinks(
|
||||
source_document_id=str(link.source_document_id),
|
||||
source_document_title=source_doc.title,
|
||||
source_document_book_id=str(book.id) if book else None,
|
||||
source_content_type="document", # 일반 문서
|
||||
target_document_id=str(link.target_document_id),
|
||||
target_document_title=document.title,
|
||||
selected_text=link.selected_text, # 소스 문서에서 선택한 텍스트 (참고용)
|
||||
@@ -509,7 +516,57 @@ async def get_document_backlinks(
|
||||
created_at=link.created_at.isoformat()
|
||||
))
|
||||
|
||||
print(f"✅ 총 {len(backlinks)}개의 백링크 반환")
|
||||
# 2. 노트에서 오는 백링크 (NoteLink) - 동기 쿼리 사용
|
||||
try:
|
||||
from ...core.database import get_sync_db
|
||||
sync_db = next(get_sync_db())
|
||||
|
||||
# 노트에서 이 문서를 대상으로 하는 링크들 조회
|
||||
note_links = sync_db.query(NoteLink).join(
|
||||
NoteDocument, NoteLink.source_note_id == NoteDocument.id
|
||||
).outerjoin(Notebook, NoteDocument.notebook_id == Notebook.id).filter(
|
||||
NoteLink.target_document_id == document_id
|
||||
).all()
|
||||
|
||||
print(f"🔍 노트 백링크 쿼리 실행 완료: {len(note_links)}개 발견")
|
||||
|
||||
# 노트 백링크 처리
|
||||
for link in note_links:
|
||||
source_note = sync_db.query(NoteDocument).filter(NoteDocument.id == link.source_note_id).first()
|
||||
notebook = sync_db.query(Notebook).filter(Notebook.id == source_note.notebook_id).first() if source_note else None
|
||||
|
||||
if source_note:
|
||||
print(f"📋 노트 백링크 발견: {source_note.title} -> {document.title}")
|
||||
print(f" - 소스 텍스트 (selected_text): {link.selected_text}")
|
||||
print(f" - 타겟 텍스트 (target_text): {link.target_text}")
|
||||
print(f" - 타겟 오프셋: {link.target_start_offset}-{link.target_end_offset}")
|
||||
print(f" - 링크 타입: {link.link_type}")
|
||||
|
||||
backlinks.append(BacklinkResponse(
|
||||
id=str(link.id),
|
||||
source_document_id=str(link.source_note_id), # 노트 ID를 문서 ID로 사용
|
||||
source_document_title=f"📝 {source_note.title}", # 노트임을 표시
|
||||
source_document_book_id=str(notebook.id) if notebook else None,
|
||||
source_content_type="note", # 노트 문서
|
||||
target_document_id=str(link.target_document_id) if link.target_document_id else document_id,
|
||||
target_document_title=document.title,
|
||||
selected_text=link.selected_text,
|
||||
start_offset=link.start_offset,
|
||||
end_offset=link.end_offset,
|
||||
link_text=link.link_text,
|
||||
description=link.description,
|
||||
link_type=link.link_type,
|
||||
target_text=link.target_text,
|
||||
target_start_offset=link.target_start_offset,
|
||||
target_end_offset=link.target_end_offset,
|
||||
created_at=link.created_at.isoformat() if link.created_at else None
|
||||
))
|
||||
|
||||
sync_db.close()
|
||||
except Exception as e:
|
||||
print(f"❌ 노트 백링크 조회 실패: {e}")
|
||||
|
||||
print(f"✅ 총 {len(backlinks)}개의 백링크 반환 (문서 + 노트)")
|
||||
return backlinks
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user