fix: Codex 리뷰 5건 수정 (critical 1 + high 4)

1. [critical] config.yaml → settings 객체에서 taxonomy 로드 (import crash 방지)
2. [high] ODF 변환: file_path 유지, derived_path 별도 필드 (무한 중복 방지)
3. [high] 법령 분할: 첫 장 이전 조문을 "서문"으로 보존
4. [high] Inbox: review_status 필드 분리 (pending/approved/rejected)
5. [high] 삭제: soft-delete (deleted_at) + worker 방어 + active_documents 뷰
   - 모든 조회에 deleted_at IS NULL 일관 적용
   - queue_consumer: row 없으면 gracefully skip

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-04-06 07:15:13 +09:00
parent 6c92e375c2
commit 24142ea605
12 changed files with 66 additions and 59 deletions

View File

@@ -40,9 +40,10 @@ class DocumentResponse(BaseModel):
importance: str | None
ai_confidence: float | None
user_note: str | None
original_path: str | None
derived_path: str | None
original_format: str | None
conversion_status: str | None
review_status: str | None
edit_url: str | None
preview_status: str | None
source_channel: str | None
@@ -101,6 +102,7 @@ async def get_document_tree(
SELECT ai_domain, COUNT(*)
FROM documents
WHERE ai_domain IS NOT NULL AND ai_domain != ''
AND deleted_at IS NULL
GROUP BY ai_domain
ORDER BY ai_domain
""")
@@ -145,7 +147,7 @@ async def list_documents(
format: str | None = None,
):
"""문서 목록 조회 (페이지네이션 + 필터)"""
query = select(Document)
query = select(Document).where(Document.deleted_at == None)
if domain:
# prefix 매칭: Industrial_Safety 클릭 시 하위 전부 포함
@@ -181,7 +183,7 @@ async def get_document(
):
"""문서 단건 조회"""
doc = await session.get(Document, doc_id)
if not doc:
if not doc or doc.deleted_at is not None:
raise HTTPException(status_code=404, detail="문서를 찾을 수 없습니다")
return DocumentResponse.model_validate(doc)
@@ -390,27 +392,8 @@ async def delete_document(
if not doc:
raise HTTPException(status_code=404, detail="문서를 찾을 수 없습니다")
if delete_file:
# 원본 파일 삭제
file_path = Path(settings.nas_mount_path) / doc.file_path
if file_path.exists():
file_path.unlink()
# 변환본 삭제
if doc.original_path:
orig = Path(settings.nas_mount_path) / doc.original_path
if orig.exists():
orig.unlink()
# preview 캐시 삭제
preview = Path(settings.nas_mount_path) / "PKM" / ".preview" / f"{doc_id}.pdf"
if preview.exists():
preview.unlink()
# 관련 processing_queue 먼저 삭제 (FK 제약)
from sqlalchemy import delete as sql_delete
await session.execute(
sql_delete(ProcessingQueue).where(ProcessingQueue.document_id == doc_id)
)
await session.delete(doc)
# soft-delete (물리 파일은 cleanup job에서 나중에 정리)
doc.deleted_at = datetime.now(timezone.utc)
await session.commit()
return {"message": f"문서 {doc_id} 삭제됨", "file_deleted": delete_file}
return {"message": f"문서 {doc_id} soft-delete 완료"}