"""admin 运营配置:列出所有可配项 + 改某项(带审计 + 改值校验)。 配置项定义见 app.core.config_schema.CONFIG_DEFS;业务读配置 fallback 默认(见 app_config repo)。 权限:operator / finance 都可改(运营调奖励、财务调额度),super 恒可。 """ from __future__ import annotations from typing import Annotated, Any from fastapi import APIRouter, Depends, HTTPException, Request from app.admin.audit import write_audit from app.admin.deps import AdminDb, get_client_ip, get_current_admin, require_role from app.admin.schemas.config import ConfigItemOut, ConfigUpdateRequest from app.core.config_schema import CONFIG_DEFS from app.models.admin import AdminUser from app.repositories import app_config router = APIRouter( prefix="/admin/api/config", tags=["admin-config"], dependencies=[Depends(get_current_admin)], ) def _validate(key: str, value: Any) -> None: """按配置项 type 校验新值,不合法抛 ValueError(router 转 400)。""" t = CONFIG_DEFS[key]["type"] if t == "int": if not isinstance(value, int) or isinstance(value, bool) or value < 0: raise ValueError("需为非负整数") elif t == "int_list": if not isinstance(value, list) or not value: raise ValueError("需为非空整数列表") if not all(isinstance(x, int) and not isinstance(x, bool) and x >= 0 for x in value): raise ValueError("列表元素需为非负整数") if key == "signin_rewards" and len(value) != 14: raise ValueError("签到档位必须正好 14 个(对应 14 天循环)") elif t == "dict_str_int": if not isinstance(value, dict) or not all( isinstance(k, str) and isinstance(v, int) and not isinstance(v, bool) for k, v in value.items() ): raise ValueError("需为 {字符串: 整数} 映射") def _item(db, key: str) -> ConfigItemOut: for item in app_config.list_all(db): if item["key"] == key: return ConfigItemOut(**item) raise HTTPException(status_code=404, detail="未知配置项") @router.get("", response_model=list[ConfigItemOut], summary="所有可配项 + 当前值") def list_config(db: AdminDb) -> list[ConfigItemOut]: return [ConfigItemOut(**item) for item in app_config.list_all(db)] @router.patch("/{key}", response_model=ConfigItemOut, summary="改某项配置(带审计)") def update_config( key: str, body: ConfigUpdateRequest, request: Request, admin: Annotated[AdminUser, Depends(require_role("operator", "finance"))], db: AdminDb, ) -> ConfigItemOut: if key not in CONFIG_DEFS: raise HTTPException(status_code=404, detail="未知配置项") try: _validate(key, body.value) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) from e before = app_config.get_value(db, key) app_config.set_value(db, key, body.value, admin_id=admin.id, commit=False) write_audit( db, admin, action="config.set", target_type="config", target_id=key, detail={"before": before, "after": body.value}, ip=get_client_ip(request), commit=False, ) db.commit() return _item(db, key)