- Event ORM model with status/level enums, domain categories, review tracking - Alembic migration e7f3a9b1c5d0 for events table - Event crawler service with placeholder data (12 sample events) - GET /api/v1/events with status/domain/search filtering and overdue_days - EventArchivePage with filter toolbar (status, domain, search) and adaptive card grid - Status badges, level badges, domain tags, overdue markers on event cards - Loading/error/empty states, hover-to-honey card interaction Co-Authored-By: Claude <noreply@anthropic.com>
66 lines
2.2 KiB
Python
66 lines
2.2 KiB
Python
"""Events API — 事件档案列表,三维筛选。"""
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.database import get_db
|
|
from app.middleware.auth import require_auth
|
|
from app.models.event import Event, EventStatus, DOMAINS
|
|
from app.models.user import User
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/events", tags=["events"])
|
|
|
|
|
|
@router.get("")
|
|
async def list_events(
|
|
status: str | None = Query(None, description="按状态筛选"),
|
|
domain: str | None = Query(None, description="按领域筛选"),
|
|
search: str = Query("", description="按标题模糊搜索"),
|
|
_user: User = Depends(require_auth),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""事件卡片列表,支持三维筛选。"""
|
|
stmt = select(Event).order_by(Event.occurred_date.desc())
|
|
|
|
valid_statuses = {s.value for s in EventStatus}
|
|
if status and status in valid_statuses:
|
|
stmt = stmt.where(Event.status == status)
|
|
|
|
if domain and domain in DOMAINS:
|
|
stmt = stmt.where(Event.domain == domain)
|
|
|
|
if search:
|
|
stmt = stmt.where(Event.title.ilike(f"%{search}%"))
|
|
|
|
result = await db.execute(stmt)
|
|
events = result.scalars().all()
|
|
|
|
now = datetime.now(timezone.utc)
|
|
data = []
|
|
for ev in events:
|
|
overdue_days = 0
|
|
if ev.next_review and ev.next_review < now:
|
|
overdue_days = (now - ev.next_review).days
|
|
|
|
data.append({
|
|
"id": ev.id,
|
|
"title": ev.title,
|
|
"level": ev.level.value,
|
|
"domain": ev.domain,
|
|
"status": ev.status.value,
|
|
"occurred_date": ev.occurred_date.isoformat(),
|
|
"last_reviewed": ev.last_reviewed.isoformat() if ev.last_reviewed else None,
|
|
"no_new_info_count": ev.no_new_info_count,
|
|
"next_review": ev.next_review.isoformat() if ev.next_review else None,
|
|
"overdue_days": overdue_days,
|
|
"created_at": ev.created_at.isoformat() if ev.created_at else None,
|
|
"updated_at": ev.updated_at.isoformat() if ev.updated_at else None,
|
|
})
|
|
|
|
return {"events": data}
|