feat: 뉴스 전용 페이지 + 분류 격리 + 읽음 상태

- /news 전용 페이지: 신문사 필터, 읽지않음 필터, 시간순 리스트, 미리보기
- 뉴스 분류 격리: ai_domain='News', classify 제거, embed만 등록
- is_read: 클릭 시 자동 읽음, 전체 읽음 API
- documents 목록에서 뉴스 제외 (source_channel != 'news')
- nav에 뉴스 링크 추가
- GET /api/news/articles, POST /api/news/mark-all-read

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-04-06 14:16:00 +09:00
parent cd5f1c526d
commit 7ca3abf17c
7 changed files with 280 additions and 9 deletions

View File

@@ -43,6 +43,7 @@ class DocumentResponse(BaseModel):
derived_path: str | None
original_format: str | None
conversion_status: str | None
is_read: bool | None
review_status: str | None
edit_url: str | None
preview_status: str | None
@@ -146,8 +147,8 @@ async def list_documents(
source: str | None = None,
format: str | None = None,
):
"""문서 목록 조회 (페이지네이션 + 필터)"""
query = select(Document).where(Document.deleted_at == None)
"""문서 목록 조회 (페이지네이션 + 필터, 뉴스 제외)"""
query = select(Document).where(Document.deleted_at == None, Document.source_channel != "news")
if domain:
# prefix 매칭: Industrial_Safety 클릭 시 하위 전부 포함

View File

@@ -98,6 +98,62 @@ async def delete_source(
return {"message": f"소스 {source_id} 삭제됨"}
@router.get("/articles")
async def list_articles(
user: Annotated[User, Depends(get_current_user)],
session: Annotated[AsyncSession, Depends(get_session)],
source: str | None = None,
unread_only: bool = False,
page: int = 1,
page_size: int = 30,
):
"""뉴스 기사 목록"""
from sqlalchemy import func
from models.document import Document
query = select(Document).where(
Document.source_channel == "news",
Document.deleted_at == None,
)
if source:
query = query.where(Document.ai_sub_group == source)
if unread_only:
query = query.where(Document.is_read == False)
count_q = select(func.count()).select_from(query.subquery())
total = (await session.execute(count_q)).scalar()
query = query.order_by(Document.is_read.asc(), Document.created_at.desc())
query = query.offset((page - 1) * page_size).limit(page_size)
result = await session.execute(query)
items = result.scalars().all()
from api.documents import DocumentResponse
return {
"items": [DocumentResponse.model_validate(doc) for doc in items],
"total": total,
"page": page,
}
@router.post("/mark-all-read")
async def mark_all_read(
user: Annotated[User, Depends(get_current_user)],
session: Annotated[AsyncSession, Depends(get_session)],
):
"""전체 읽음 처리"""
from sqlalchemy import update
from models.document import Document
result = await session.execute(
update(Document)
.where(Document.source_channel == "news", Document.is_read == False)
.values(is_read=True)
)
await session.commit()
return {"marked": result.rowcount}
@router.post("/collect")
async def trigger_collect(
user: Annotated[User, Depends(get_current_user)],