This commit is contained in:
2026-05-24 01:16:07 +08:00
commit 2ece5174a7
35 changed files with 2583 additions and 0 deletions

96
backend/app/api/news.py Normal file
View File

@@ -0,0 +1,96 @@
from datetime import date, datetime
from typing import Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy import select, func, distinct
from sqlalchemy.ext.asyncio import AsyncSession
from ..database import get_db
from ..models.news import ProcessedNews, RawNews
router = APIRouter()
def _serialize(n: ProcessedNews) -> dict:
raw = n.raw_news
return {
"id": n.id,
"title_zh": n.title_zh,
"summary": n.summary,
"opinion": n.opinion,
"keywords": n.keywords or [],
"importance_score": n.importance_score,
"importance_reason": n.importance_reason,
"category": n.category,
"is_featured": n.is_featured,
"featured_rank": n.featured_rank,
"source_name": n.source_name or (raw.source.name if raw and raw.source else ""),
"source_url": n.source_url or (raw.url if raw else ""),
"published_at": n.published_at.isoformat() if n.published_at else None,
"processed_at": n.processed_at.isoformat() if n.processed_at else None,
}
@router.get("/featured")
async def get_featured(
news_date: Optional[str] = Query(default=None, alias="date"),
db: AsyncSession = Depends(get_db),
):
target = date.fromisoformat(news_date) if news_date else date.today()
stmt = (
select(ProcessedNews)
.join(ProcessedNews.raw_news)
.where(ProcessedNews.is_featured == True)
.where(func.date(ProcessedNews.processed_at) == target)
.order_by(ProcessedNews.featured_rank)
)
result = await db.execute(stmt)
items = result.scalars().all()
return {"date": str(target), "items": [_serialize(n) for n in items]}
@router.get("")
async def get_news(
news_date: Optional[str] = Query(default=None, alias="date"),
category: Optional[str] = Query(default=None),
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=100),
db: AsyncSession = Depends(get_db),
):
target = date.fromisoformat(news_date) if news_date else date.today()
stmt = (
select(ProcessedNews)
.join(ProcessedNews.raw_news)
.where(func.date(ProcessedNews.processed_at) == target)
)
if category:
stmt = stmt.where(ProcessedNews.category == category)
count_stmt = select(func.count()).select_from(stmt.subquery())
total = (await db.execute(count_stmt)).scalar_one()
stmt = stmt.order_by(ProcessedNews.importance_score.desc()).offset((page - 1) * page_size).limit(page_size)
result = await db.execute(stmt)
items = result.scalars().all()
return {"date": str(target), "total": total, "page": page, "items": [_serialize(n) for n in items]}
@router.get("/dates")
async def get_dates(db: AsyncSession = Depends(get_db)):
stmt = select(
func.date(ProcessedNews.processed_at).label("d"),
func.count(ProcessedNews.id).label("cnt"),
).group_by("d").order_by(func.date(ProcessedNews.processed_at).desc()).limit(30)
result = await db.execute(stmt)
return [{"date": str(row.d), "count": row.cnt} for row in result]
@router.get("/{news_id}")
async def get_news_detail(news_id: int, db: AsyncSession = Depends(get_db)):
stmt = select(ProcessedNews).join(ProcessedNews.raw_news).where(ProcessedNews.id == news_id)
result = await db.execute(stmt)
news = result.scalar_one_or_none()
if not news:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="Not found")
return _serialize(news)