{"repo": "arkyan712/mycchackathon", "files": {"backend/app/routers/settings.py": {"1fc13bfa9275e0d671a99de37957644a20ce6cb1": {"t": 1785746906.498971, "n": 5511, "h": "80b77988e592b716", "c": "\"\"\"System settings API for runtime AI provider configuration.\"\"\"\n\nfrom pathlib import Path\n\nfrom fastapi import APIRouter, Depends, HTTPException\nfrom pydantic import BaseModel\nfrom sqlalchemy import select as _s\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\nfrom app.core.config import BACKEND_DIR\nfrom app.core.database import get_db\nfrom app.core.deps import get_current_user\nfrom app.integrations.client import AIClient, apply_runtime_config\nfrom app.models.system_config import SystemConfig\nfrom app.models.user import User\n\nrouter = APIRouter(prefix=\"/api/settings\", tags=[\"settings\"])\n\n\nclass SettingsUpdate(BaseModel):\n deepseek_api_key: str = \"\"\n\n\nclass SettingsApiKeyCheck(BaseModel):\n deepseek_api_key: str = \"\"\n\n\ndef validate_api_key_format(key: str) -> None:\n if key and not key.startswith(\"sk-\"):\n raise HTTPException(400, \"API Key 格式不正确,应以 sk- 开头\")\n if \"\\n\" in key or \"\\r\" in key:\n raise HTTPException(400, \"API Key 不能包含换行符\")\n\n\n@router.get(\"\")\nasync def get_settings(\n user: User = Depends(get_current_user),\n db: AsyncSession = Depends(get_db),\n):\n r = await db.execute(_s(SystemConfig))\n configs = {c.key: c.value for c in r.scalars().all()}\n key = configs.get(\"deepseek_api_key\", \"\")\n has_key = bool(key)\n masked = key[:7] + \"***\" + key[-4:] if len(key) > 10 else (\"***\" if key else \"\")\n return {\n \"has_api_key\": has_key,\n \"api_key_masked\": masked,\n \"base_url\": configs.get(\"deepseek_base_url\", \"https://api.deepseek.com\"),\n \"pro_model\": configs.get(\"deepseek_pro_model\", \"deepseek-v4-pro\"),\n \"flash_model\": configs.get(\"deepseek_flash_model\", \"deepseek-v4-flash\"),\n }\n\n\n@router.put(\"\")\nasync def update_settings(\n data: SettingsUpdate,\n user: User = Depends(get_current_user),\n db: AsyncSession = Depends(get_db),\n):\n key = data.deepseek_api_key.strip()\n validate_api_key_format(key)\n\n if key:\n await _upsert(db, \"deepseek_api_key\", key)\n persist_env_value(\"DEEPSEEK_API_KEY\", key)\n apply_runtime_config(deepseek_api_key=key)\n return {\"ok\": True, \"message\": \"设置已保存并立即生效\"}\n\n\n@router.post(\"/test-api-key\")\nasync def test_api_key(\n data: SettingsApiKeyCheck,\n user: User = Depends(get_current_user),\n db: AsyncSession = Depends(get_db),\n):\n key = data.deepseek_api_key.strip()\n if not key:\n raise HTTPException(400, \"请输入 API Key\")\n validate_api_key_format(key)\n\n base_url = await get_config_value(db, \"deepseek_base_url\", \"https://api.deepseek.com\")\n flash_model = await get_config_value(db, \"deepseek_flash_model\", \"deepseek-v4-flash\")\n client = AIClient(api_key=key, base_url=base_url, flash_model=flash_model)\n try:\n await client.chat(\n [{\"role\": \"user\", \"content\": \"请只回复 ok,用于检测 API Key 是否可用。\"}],\n model=flash_model,\n temperature=0,\n max_tokens=8,\n timeout=8,\n max_retries=0,\n )\n path_text = \"本地代理\" if client.last_connection_path == \"local_proxy\" else \"默认网络\"\n return {\n \"ok\": True,\n \"message\": f\"API Key 可用,当前通过{path_text}连接 DeepSeek\",\n \"connection_path\": client.last_connection_path,\n }\n except Exception as exc:\n return {\"ok\": False, \"message\": explain_api_check_error(exc), \"connection_path\": client.last_connection_path}\n\n\ndef explain_api_check_error(exc: Exception) -> str:\n message = str(exc)\n lowered = message.lower()\n if \"api error 401\" in lowered or \"api error 403\" in lowered:\n return \"API Key 无效或没有访问权限\"\n if \"api error 402\" in lowered:\n return \"账户余额不足或额度受限\"\n if \"timed out\" in lowered or \"timeout\" in lowered:\n return \"检测超时,请检查网络或稍后重试\"\n if \"connect\" in lowered or \"connection\" in lowered:\n return \"默认网络和本地代理都无法连接 DeepSeek,请检查网络、代理或 API 地址\"\n if \"api error 400\" in lowered:\n return \"请求被模型服务拒绝,请检查模型配置\"\n return f\"检测失败:{message[:120]}\"\n\n\n@router.get(\"/status\")\nasync def api_status():\n \"\"\"Public endpoint for checking whether an API key is configured.\"\"\"\n from app.core.database import async_session\n\n async with async_session() as db:\n r = await db.execute(_s(SystemConfig))\n configs = {c.key: c.value for c in r.scalars().all()}\n has_key = bool(configs.get(\"deepseek_api_key\", \"\"))\n return {\"configured\": has_key}\n\n\nasync def _upsert(db: AsyncSession, key: str, value: str):\n r = await db.execute(_s(SystemConfig).where(SystemConfig.key == key))\n existing = r.scalar_one_or_none()\n if existing:\n existing.value = value\n else:\n db.add(SystemConfig(key=key, value=value))\n await db.commit()\n\n\ndef persist_env_value(key: str, value: str, env_path: str | Path | None = None) -> None:\n path = Path(env_path) if env_path is not None else BACKEND_DIR / \".env\"\n lines = path.read_text(encoding=\"utf-8\").splitlines() if path.exists() else []\n prefix = f\"{key}=\"\n updated: list[str] = []\n replaced = False\n\n for line in lines:\n if line.startswith(prefix):\n updated.append(f\"{key}={value}\")\n replaced = True\n else:\n updated.append(line)\n\n if not replaced:\n updated.append(f\"{key}={value}\")\n\n path.write_text(\"\\n\".join(updated) + \"\\n\", encoding=\"utf-8\")\n\n\nasync def get_config_value(db: AsyncSession, key: str, default: str = \"\") -> str:\n r = await db.execute(_s(SystemConfig).where(SystemConfig.key == key))\n c = r.scalar_one_or_none()\n return c.value if c else default\n"}, "485a51ea2055132bd1e72c9c1cdf1a67bca5d3c2": {"t": 1785768789.4849508, "n": 3552, "h": "3477eaed9e7276bc", "c": "\"\"\"系统设置 API — 让用户配置 API Key 等参数。\"\"\"\n\nfrom pathlib import Path\n\nfrom pydantic import BaseModel\n\nfrom fastapi import APIRouter, Depends, HTTPException\nfrom sqlalchemy import select as _s\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\nfrom app.core.config import BACKEND_DIR\nfrom app.core.database import get_db\nfrom app.core.deps import get_current_user\nfrom app.integrations.client import apply_runtime_config\nfrom app.models.system_config import SystemConfig\nfrom app.models.user import User\n\nrouter = APIRouter(prefix=\"/api/settings\", tags=[\"settings\"])\n\n\nclass SettingsUpdate(BaseModel):\n deepseek_api_key: str = \"\"\n\n\n@router.get(\"\")\nasync def get_settings(\n user: User = Depends(get_current_user),\n db: AsyncSession = Depends(get_db),\n):\n r = await db.execute(_s(SystemConfig))\n configs = {c.key: c.value for c in r.scalars().all()}\n # Mask API key for security, also check if configured\n key = configs.get(\"deepseek_api_key\", \"\")\n has_key = bool(key)\n masked = key[:7] + \"***\" + key[-4:] if len(key) > 10 else (\"***\" if key else \"\")\n return {\n \"has_api_key\": has_key,\n \"api_key_masked\": masked,\n \"base_url\": configs.get(\"deepseek_base_url\", \"https://api.deepseek.com\"),\n \"pro_model\": configs.get(\"deepseek_pro_model\", \"deepseek-v4-pro\"),\n \"flash_model\": configs.get(\"deepseek_flash_model\", \"deepseek-v4-flash\"),\n }\n\n\n@router.put(\"\")\nasync def update_settings(\n data: SettingsUpdate,\n user: User = Depends(get_current_user),\n db: AsyncSession = Depends(get_db),\n):\n key = data.deepseek_api_key.strip()\n if key and not key.startswith(\"sk-\"):\n raise HTTPException(400, \"API Key 格式不正确,应以 sk- 开头\")\n if \"\\n\" in key or \"\\r\" in key:\n raise HTTPException(400, \"API Key 不能包含换行符\")\n\n if key:\n await _upsert(db, \"deepseek_api_key\", key)\n persist_env_value(\"DEEPSEEK_API_KEY\", key)\n apply_runtime_config(deepseek_api_key=key)\n return {\"ok\": True, \"message\": \"设置已保存并立即生效\"}\n\n\n@router.get(\"/status\")\nasync def api_status():\n \"\"\"公开端点:检查 API 是否已配置。\"\"\"\n from app.core.database import async_session\n async with async_session() as db:\n r = await db.execute(_s(SystemConfig))\n configs = {c.key: c.value for c in r.scalars().all()}\n has_key = bool(configs.get(\"deepseek_api_key\", \"\"))\n return {\"configured\": has_key}\n\n\nasync def _upsert(db: AsyncSession, key: str, value: str):\n r = await db.execute(_s(SystemConfig).where(SystemConfig.key == key))\n existing = r.scalar_one_or_none()\n if existing:\n existing.value = value\n else:\n db.add(SystemConfig(key=key, value=value))\n await db.commit()\n\n\ndef persist_env_value(key: str, value: str, env_path: str | Path | None = None) -> None:\n path = Path(env_path) if env_path is not None else BACKEND_DIR / \".env\"\n lines = path.read_text(encoding=\"utf-8\").splitlines() if path.exists() else []\n prefix = f\"{key}=\"\n updated: list[str] = []\n replaced = False\n\n for line in lines:\n if line.startswith(prefix):\n updated.append(f\"{key}={value}\")\n replaced = True\n else:\n updated.append(line)\n\n if not replaced:\n updated.append(f\"{key}={value}\")\n\n path.write_text(\"\\n\".join(updated) + \"\\n\", encoding=\"utf-8\")\n\n\nasync def get_config_value(db: AsyncSession, key: str, default: str = \"\") -> str:\n \"\"\"从系统配置表中读取值,不存在则返回默认值。\"\"\"\n r = await db.execute(_s(SystemConfig).where(SystemConfig.key == key))\n c = r.scalar_one_or_none()\n return c.value if c else default\n"}, "dcbb7bd15b533b83faf46556eaeb15d62dd38324": {"t": 1785768791.0358276, "n": 2604, "h": "7b9a6a2cde23b895", "c": "\"\"\"系统设置 API — 让用户配置 API Key 等参数。\"\"\"\n\nfrom pydantic import BaseModel\n\nfrom fastapi import APIRouter, Depends, HTTPException\nfrom sqlalchemy import select as _s\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\nfrom app.core.database import get_db\nfrom app.core.deps import get_current_user\nfrom app.models.system_config import SystemConfig\nfrom app.models.user import User\n\nrouter = APIRouter(prefix=\"/api/settings\", tags=[\"settings\"])\n\n\nclass SettingsUpdate(BaseModel):\n deepseek_api_key: str = \"\"\n\n\n@router.get(\"\")\nasync def get_settings(\n user: User = Depends(get_current_user),\n db: AsyncSession = Depends(get_db),\n):\n r = await db.execute(_s(SystemConfig))\n configs = {c.key: c.value for c in r.scalars().all()}\n # Mask API key for security, also check if configured\n key = configs.get(\"deepseek_api_key\", \"\")\n has_key = bool(key)\n masked = key[:7] + \"***\" + key[-4:] if len(key) > 10 else (\"***\" if key else \"\")\n return {\n \"has_api_key\": has_key,\n \"api_key_masked\": masked,\n \"base_url\": configs.get(\"deepseek_base_url\", \"https://api.deepseek.com\"),\n \"pro_model\": configs.get(\"deepseek_pro_model\", \"deepseek-v4-pro\"),\n \"flash_model\": configs.get(\"deepseek_flash_model\", \"deepseek-v4-flash\"),\n }\n\n\n@router.put(\"\")\nasync def update_settings(\n data: SettingsUpdate,\n user: User = Depends(get_current_user),\n db: AsyncSession = Depends(get_db),\n):\n key = data.deepseek_api_key.strip()\n if key and not key.startswith(\"sk-\"):\n raise HTTPException(400, \"API Key 格式不正确,应以 sk- 开头\")\n\n if key:\n await _upsert(db, \"deepseek_api_key\", key)\n return {\"ok\": True, \"message\": \"设置已保存\"}\n\n\n@router.get(\"/status\")\nasync def api_status():\n \"\"\"公开端点:检查 API 是否已配置。\"\"\"\n from app.core.database import async_session\n async with async_session() as db:\n r = await db.execute(_s(SystemConfig))\n configs = {c.key: c.value for c in r.scalars().all()}\n has_key = bool(configs.get(\"deepseek_api_key\", \"\"))\n return {\"configured\": has_key}\n\n\nasync def _upsert(db: AsyncSession, key: str, value: str):\n r = await db.execute(_s(SystemConfig).where(SystemConfig.key == key))\n existing = r.scalar_one_or_none()\n if existing:\n existing.value = value\n else:\n db.add(SystemConfig(key=key, value=value))\n await db.commit()\n\n\nasync def get_config_value(db: AsyncSession, key: str, default: str = \"\") -> str:\n \"\"\"从系统配置表中读取值,不存在则返回默认值。\"\"\"\n r = await db.execute(_s(SystemConfig).where(SystemConfig.key == key))\n c = r.scalar_one_or_none()\n return c.value if c else default\n"}}}}