"""System configuration API — get/set runtime settings like notification time.""" from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from app.database import get_db from app.middleware.auth import get_current_user, require_director from app.models.system_config import SystemConfig from app.services.holidays import refresh_holidays from app.services.scheduler_manager import reschedule_daily_check router = APIRouter(prefix="/system-config", tags=["SystemConfig"]) DEFAULT_NOTIFICATION_TIME = "17:30" def _parse_time(time_str: str) -> tuple[int, int]: """Parse 'HH:MM' string to (hour, minute). Raises ValueError on bad input.""" parts = time_str.strip().split(":") if len(parts) != 2: raise ValueError("时间格式必须为 HH:MM") h, m = int(parts[0]), int(parts[1]) if not (0 <= h <= 23 and 0 <= m <= 59): raise ValueError("小时 0-23,分钟 0-59") return h, m async def _get_config(db: AsyncSession, key: str) -> str | None: row = await db.get(SystemConfig, key) return row.value if row else None async def _set_config(db: AsyncSession, key: str, value: str): row = await db.get(SystemConfig, key) if row: row.value = value else: db.add(SystemConfig(key=key, value=value)) await db.commit() async def get_notification_time(db: AsyncSession) -> str: """Read notification_time from DB, falling back to default.""" val = await _get_config(db, "notification_time") return val if val else DEFAULT_NOTIFICATION_TIME @router.get("") async def list_config( current_user: dict = Depends(require_director), db: AsyncSession = Depends(get_db), ): """Return all system config as {key: value} dict.""" result = await db.execute(select(SystemConfig)) rows = result.scalars().all() config = {r.key: r.value for r in rows} # Ensure notification_time always has a value if "notification_time" not in config: config["notification_time"] = DEFAULT_NOTIFICATION_TIME return config @router.put("/{key}") async def update_config( key: str, body: dict, current_user: dict = Depends(require_director), db: AsyncSession = Depends(get_db), ): """Update a single config key. Only directors can change settings.""" value = body.get("value", "") valid_keys = {"notification_time", "holidays", "ai_summary_prompt"} if key not in valid_keys: raise HTTPException(status_code=400, detail=f"不支持的配置项: {key}") if key == "notification_time": try: h, m = _parse_time(value) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) await _set_config(db, key, value) await reschedule_daily_check(h, m) return {"key": key, "value": value, "scheduled": f"{h:02d}:{m:02d}"} if key == "holidays": # Validate: comma-separated YYYY-MM-DD dates if value.strip(): for s in value.split(","): s = s.strip() if s: try: from datetime import date date.fromisoformat(s) except ValueError: raise HTTPException(status_code=400, detail=f"日期格式错误: {s},应为 YYYY-MM-DD") await _set_config(db, key, value) return {"key": key, "value": value} await _set_config(db, key, value) return {"key": key, "value": value} @router.post("/refresh-holidays") async def refresh_holidays_endpoint( current_user: dict = Depends(require_director), db: AsyncSession = Depends(get_db), ): """Fetch the latest Chinese holiday data from apisbo.com and cache it.""" try: result = await refresh_holidays(db) return {"status": "ok", **result} except Exception as e: raise HTTPException(status_code=500, detail=f"刷新节假日数据失败: {str(e)}")