"""管理路由""" from fastapi import APIRouter, Request, HTTPException, Depends from app.database import get_connection from app.routers.auth import verify_token from app.services.backup import manual_backup, auto_backup, get_backup_history from app.services.indexer import incremental_update router = APIRouter() @router.get("/admin/backup-config") def get_backup_config(request: Request, _=Depends(verify_token)): """查看 WebDAV 配置""" config = request.app.state.config return { "enabled": config.webdav_enabled, "url": config.webdav_url, "username": config.webdav_username, "configured": bool(config.webdav_enabled and config.webdav_url), } @router.put("/admin/backup-config") def set_backup_config(request: Request, body: dict, _=Depends(verify_token)): """设置 WebDAV 配置""" config = request.app.state.config config.set_webdav( url=body.get("url", ""), username=body.get("username", ""), password=body.get("password", ""), remote_path=body.get("remote_path", "/tiku-backup"), ) return {"ok": True} @router.post("/admin/backup/trigger") def trigger_backup(request: Request, _=Depends(verify_token)): """手动触发增量备份""" config = request.app.state.config try: auto_backup(config) return {"ok": True} except Exception as e: raise HTTPException(status_code=500, detail={ "error": {"code": "backup_failed", "message": str(e)} }) @router.post("/admin/full-backup") def trigger_full_backup(request: Request, _=Depends(verify_token)): """手动触发全量备份(bank.db + 全部 docx)""" config = request.app.state.config try: from app.services.backup import full_backup backup_dir = full_backup(config) return {"ok": True, "path": str(backup_dir.name)} except Exception as e: raise HTTPException(status_code=500, detail={ "error": {"code": "full_backup_failed", "message": str(e)} }) @router.get("/admin/backup/history") def backup_history(request: Request, _=Depends(verify_token)): """备份历史""" config = request.app.state.config return {"backups": get_backup_history(config)} @router.post("/admin/clear-all") def clear_all(request: Request, _=Depends(verify_token)): """清空题库(删除所有题目和数据库索引,保留知识点)""" config = request.app.state.config try: # Delete all docx files for docx in config.problems_dir.glob("*.docx"): docx.unlink() # Delete DB and rebuild schema + knowledge conn = get_connection(config.db_path) conn.execute("DELETE FROM problems") conn.execute("DELETE FROM problems_fts") conn.execute("DELETE FROM cart_items") conn.execute("DELETE FROM import_tasks") conn.commit() conn.close() # Rebuild index from app.services.indexer import incremental_update incremental_update(config) return {"ok": True} except Exception as e: raise HTTPException(status_code=500, detail={ "error": {"code": "clear_failed", "message": str(e)} }) @router.post("/admin/rebuild-index") def rebuild_index(request: Request, _=Depends(verify_token)): """手动增量索引""" config = request.app.state.config try: changes = incremental_update(config) return {"ok": True, "changes": changes} except Exception as e: raise HTTPException(status_code=500, detail={ "error": {"code": "rebuild_failed", "message": str(e)} }) @router.post("/admin/knowledge-build") def knowledge_build(request: Request, _=Depends(verify_token)): """构建知识点编码""" config = request.app.state.config try: from app.services.knowledge import build_knowledge build_knowledge(config) return {"ok": True} except Exception as e: raise HTTPException(status_code=500, detail={ "error": {"code": "knowledge_build_failed", "message": str(e)} }) @router.post("/admin/git-update") def git_update(request: Request, _=Depends(verify_token)): """从远端 git 拉取更新并重启服务""" import subprocess, os, signal config = request.app.state.config project_dir = config.config_path.parent try: result = subprocess.run( ["git", "pull", "origin", "master"], cwd=str(project_dir), capture_output=True, text=True, timeout=30 ) if "up to date" in result.stdout.lower() or "已经是最新" in result.stdout: return {"updated": False, "message": "已是最新版本", "git_output": result.stdout} if result.returncode != 0: raise RuntimeError(result.stderr) # 有更新,1秒后重启 import threading, sys def restart(): import time time.sleep(1) sys.exit(0) threading.Thread(target=restart, daemon=True).start() return {"updated": True, "message": "已更新,正在重启...", "git_output": result.stdout} except subprocess.TimeoutExpired as e: raise HTTPException(status_code=500, detail={ "error": {"code": "git_timeout", "message": "git pull 超时"} }) except Exception as e: raise HTTPException(status_code=500, detail={ "error": {"code": "git_failed", "message": str(e)} })