# Copyright (c) 2025-2026 马建军. All rights reserved. """HTTP routes for backup module.""" from __future__ import annotations import logging from flask import jsonify, request, send_file logger = logging.getLogger(__name__) def register(deps) -> None: app = deps.app login_required = deps.login_required get_setting = deps.get_setting from modules.backup.db_backup import ( RESTORE_CONFIRM_TOKEN, backup_dir, backup_in_progress, get_backup_last_at, get_restore_status, inspect_backup_archive, list_backups, resolve_backup_file, restore_in_progress, save_uploaded_backup, schedule_restore, ) @app.route("/api/backup/list") @login_required def api_backup_list(): return jsonify( { "dir": str(backup_dir()), "last_at": get_backup_last_at(get_setting), "running": backup_in_progress(), "restore": get_restore_status(), "items": list_backups(), } ) @app.route("/api/backup/download/") @login_required def api_backup_download(filename): try: path = resolve_backup_file(filename) except (ValueError, FileNotFoundError) as exc: return jsonify({"error": str(exc)}), 404 return send_file(path, as_attachment=True, download_name=path.name) @app.route("/api/backup/upload", methods=["POST"]) @login_required def api_backup_upload(): if backup_in_progress(): return jsonify({"error": "备份进行中,请稍后再试"}), 409 if restore_in_progress(): return jsonify({"error": "恢复进行中,请稍后再试"}), 409 upload = request.files.get("file") if not upload or not upload.filename: return jsonify({"error": "请选择备份文件"}), 400 if not upload.filename.lower().endswith(".tar.gz"): return jsonify({"error": "仅支持 .tar.gz 备份包"}), 400 try: name, info = save_uploaded_backup(upload.stream, upload.filename) return jsonify({"ok": True, "name": name, "info": info}) except ValueError as exc: return jsonify({"error": str(exc)}), 400 except Exception: logger.exception("backup upload failed") return jsonify({"error": "上传失败,请检查备份包是否完整"}), 500 @app.route("/api/backup/info/") @login_required def api_backup_info(filename): try: path = resolve_backup_file(filename) return jsonify(inspect_backup_archive(path)) except (ValueError, FileNotFoundError) as exc: return jsonify({"error": str(exc)}), 404 @app.route("/api/backup/restore", methods=["POST"]) @login_required def api_backup_restore(): data = request.get_json(silent=True) or {} filename = (data.get("filename") or request.form.get("filename") or "").strip() confirm = (data.get("confirm") or request.form.get("confirm") or "").strip() if confirm != RESTORE_CONFIRM_TOKEN: return jsonify({"error": "请确认恢复操作"}), 400 if not filename: return jsonify({"error": "缺少备份文件名"}), 400 ok, msg = schedule_restore(filename) if ok: return jsonify({"ok": True, "message": msg}), 202 return jsonify({"error": msg}), 409 @app.route("/api/backup/restore/status") @login_required def api_backup_restore_status(): return jsonify(get_restore_status())