feat: Event archive management — Event model, migration, API, frontend page (Phase 8)
- 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>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
"""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}
|
||||
Reference in New Issue
Block a user