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:
@@ -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 클릭 시 하위 전부 포함
|
||||
|
||||
@@ -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)],
|
||||
|
||||
Reference in New Issue
Block a user