162 lines
6.8 KiB
Python
162 lines
6.8 KiB
Python
from collections.abc import AsyncGenerator
|
|
from contextlib import asynccontextmanager
|
|
from urllib.parse import quote
|
|
|
|
from fastapi import Depends, FastAPI, HTTPException, Request, status
|
|
from fastapi.responses import HTMLResponse
|
|
from fastapi.responses import Response
|
|
from fastapi.staticfiles import StaticFiles
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.db import SessionLocal, get_db_session, init_db
|
|
from app.models import Comment, ContentItem, Hotspot, Report
|
|
from app.schemas import CreateTaskRequest, CreateTaskResponse, TaskResponse
|
|
from app.services.export_service import export_hotspot_comments_csv, export_item_comments_csv, export_report_markdown
|
|
from app.services.task_service import (
|
|
RUNNING_TASK_MESSAGE,
|
|
create_task,
|
|
get_task,
|
|
has_running_task,
|
|
list_tasks,
|
|
recover_running_tasks,
|
|
)
|
|
from app.templating import templates
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(_app: FastAPI) -> AsyncGenerator[None]:
|
|
init_db()
|
|
with SessionLocal() as session:
|
|
recover_running_tasks(session)
|
|
yield
|
|
|
|
|
|
app = FastAPI(title="热榜评论分析工具", lifespan=lifespan)
|
|
app.mount("/static", StaticFiles(directory="app/static"), name="static")
|
|
|
|
|
|
@app.get("/health")
|
|
def health() -> dict[str, str]:
|
|
return {"status": "ok"}
|
|
|
|
|
|
@app.get("/", response_class=HTMLResponse)
|
|
def index_page(request: Request, session: Session = Depends(get_db_session)) -> HTMLResponse:
|
|
return templates.TemplateResponse(
|
|
request,
|
|
"index.html",
|
|
{"tasks": list_tasks(session)},
|
|
)
|
|
|
|
|
|
@app.post("/api/tasks", response_model=CreateTaskResponse, status_code=status.HTTP_201_CREATED)
|
|
def create_task_api(
|
|
request: CreateTaskRequest,
|
|
session: Session = Depends(get_db_session),
|
|
) -> CreateTaskResponse:
|
|
if has_running_task(session):
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=RUNNING_TASK_MESSAGE)
|
|
|
|
task = create_task(session, request)
|
|
return CreateTaskResponse(task_id=task.id, status=task.status)
|
|
|
|
|
|
@app.get("/api/tasks", response_model=list[TaskResponse])
|
|
def list_tasks_api(session: Session = Depends(get_db_session)) -> list[TaskResponse]:
|
|
return [TaskResponse.model_validate(task) for task in list_tasks(session)]
|
|
|
|
|
|
@app.get("/api/tasks/{task_id}", response_model=TaskResponse)
|
|
def get_task_api(task_id: str, session: Session = Depends(get_db_session)) -> TaskResponse:
|
|
task = get_task(session, task_id)
|
|
if task is None:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="任务不存在")
|
|
return TaskResponse.model_validate(task)
|
|
|
|
|
|
@app.get("/tasks/{task_id}", response_class=HTMLResponse)
|
|
def task_detail_page(task_id: str, request: Request, session: Session = Depends(get_db_session)) -> HTMLResponse:
|
|
task = get_task(session, task_id)
|
|
if task is None:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="任务不存在")
|
|
hotspots = list(session.scalars(select(Hotspot).where(Hotspot.task_id == task.id).order_by(Hotspot.rank)))
|
|
return templates.TemplateResponse(request, "tasks/detail.html", {"task": task, "hotspots": hotspots})
|
|
|
|
|
|
@app.get("/hotspots/{hotspot_id}/report", response_class=HTMLResponse)
|
|
def hotspot_report_page(hotspot_id: str, request: Request, session: Session = Depends(get_db_session)) -> HTMLResponse:
|
|
hotspot = session.get(Hotspot, hotspot_id)
|
|
if hotspot is None:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="热点不存在")
|
|
task = get_task(session, hotspot.task_id)
|
|
report = session.scalar(select(Report).where(Report.hotspot_id == hotspot.id, Report.report_type == "hotspot"))
|
|
return templates.TemplateResponse(request, "hotspots/report.html", {"task": task, "hotspot": hotspot, "report": report})
|
|
|
|
|
|
@app.get("/items/{item_id}", response_class=HTMLResponse)
|
|
def item_detail_page(item_id: str, request: Request, session: Session = Depends(get_db_session)) -> HTMLResponse:
|
|
item = session.get(ContentItem, item_id)
|
|
if item is None:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="内容条目不存在")
|
|
hotspot = session.get(Hotspot, item.hotspot_id)
|
|
task = get_task(session, item.task_id)
|
|
report = session.scalar(select(Report).where(Report.content_item_id == item.id, Report.report_type == "item"))
|
|
comments = list(
|
|
session.scalars(
|
|
select(Comment)
|
|
.where(Comment.content_item_id == item.id)
|
|
.order_by(Comment.like_count.desc().nullslast(), Comment.comment_time.desc().nullslast())
|
|
.limit(100)
|
|
)
|
|
)
|
|
return templates.TemplateResponse(
|
|
request,
|
|
"items/detail.html",
|
|
{"task": task, "hotspot": hotspot, "item": item, "report": report, "comments": comments},
|
|
)
|
|
|
|
|
|
@app.get("/api/export/items/{item_id}/comments.csv")
|
|
def export_item_comments(item_id: str, session: Session = Depends(get_db_session)) -> Response:
|
|
try:
|
|
content, filename = export_item_comments_csv(session, item_id)
|
|
except ValueError:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="导出数据不存在")
|
|
return Response(
|
|
content=content,
|
|
media_type="text/csv; charset=utf-8",
|
|
headers={"Content-Disposition": f"attachment; filename*=UTF-8''{quote(filename)}"},
|
|
)
|
|
|
|
|
|
@app.get("/api/export/hotspots/{hotspot_id}/comments.csv")
|
|
def export_hotspot_comments(hotspot_id: str, session: Session = Depends(get_db_session)) -> Response:
|
|
try:
|
|
content, filename = export_hotspot_comments_csv(session, hotspot_id)
|
|
except ValueError:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="导出数据不存在")
|
|
return Response(
|
|
content=content,
|
|
media_type="text/csv; charset=utf-8",
|
|
headers={"Content-Disposition": f"attachment; filename*=UTF-8''{quote(filename)}"},
|
|
)
|
|
|
|
|
|
@app.get("/api/export/hotspots/{hotspot_id}.md")
|
|
def export_hotspot_markdown(hotspot_id: str, session: Session = Depends(get_db_session)) -> Response:
|
|
try:
|
|
content, filename = export_report_markdown(session, report_type="hotspot", hotspot_id=hotspot_id)
|
|
except ValueError:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="报告不存在")
|
|
return Response(content=content, media_type="text/markdown; charset=utf-8", headers={"Content-Disposition": f"attachment; filename*=UTF-8''{quote(filename)}"})
|
|
|
|
|
|
@app.get("/api/export/items/{item_id}.md")
|
|
def export_item_markdown(item_id: str, session: Session = Depends(get_db_session)) -> Response:
|
|
try:
|
|
content, filename = export_report_markdown(session, report_type="item", item_id=item_id)
|
|
except ValueError:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="报告不存在")
|
|
return Response(content=content, media_type="text/markdown; charset=utf-8", headers={"Content-Disposition": f"attachment; filename*=UTF-8''{quote(filename)}"})
|