| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- """管理路由"""
- 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)}
- })
|