add: provide backup archive feature
This commit is contained in:
66
api/backup.py
Normal file
66
api/backup.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import shutil
|
||||||
|
import zipfile
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from flask import Blueprint, send_file, jsonify
|
||||||
|
import os
|
||||||
|
from api import require_auth
|
||||||
|
from db import get_db
|
||||||
|
from db.models.Markdown import Markdown
|
||||||
|
from db.models.Path import Path
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
backup_bp = Blueprint('backup', __name__, url_prefix='/api/backup')
|
||||||
|
@backup_bp.route('/', methods=['GET'])
|
||||||
|
@require_auth(roles=['admin'])
|
||||||
|
def get_backup():
|
||||||
|
try:
|
||||||
|
paths = {}
|
||||||
|
with get_db() as session:
|
||||||
|
pths = session.query(Path).all()
|
||||||
|
paths = {p.id : p for p in pths}
|
||||||
|
traverse(1, paths)
|
||||||
|
timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
|
||||||
|
archive = shutil.make_archive(base_name=timestamp, format='zip', root_dir='Root')
|
||||||
|
|
||||||
|
shutil.rmtree('Root')
|
||||||
|
return send_file(
|
||||||
|
archive,
|
||||||
|
as_attachment=True,
|
||||||
|
download_name=timestamp + '.zip',
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to get backup: {e}")
|
||||||
|
return jsonify({"error": "failed to get backup"}), 500
|
||||||
|
|
||||||
|
|
||||||
|
def create_and_cd(path_name):
|
||||||
|
if not os.path.exists(path_name) or not os.path.isdir(path_name):
|
||||||
|
os.makedirs(path_name)
|
||||||
|
os.chdir(path_name)
|
||||||
|
|
||||||
|
def cd_back():
|
||||||
|
os.chdir('..')
|
||||||
|
|
||||||
|
def traverse(path_id, paths):
|
||||||
|
current_path = paths[path_id]
|
||||||
|
if path_id == 1:
|
||||||
|
create_and_cd("Root")
|
||||||
|
else:
|
||||||
|
create_and_cd(current_path.name)
|
||||||
|
with open(".meta", "w") as meta_file:
|
||||||
|
meta_file.write(f"order: {current_path.order}\n")
|
||||||
|
with get_db() as session:
|
||||||
|
mds = session.query(Markdown).filter(Markdown.path_id == path_id).all()
|
||||||
|
for md in mds:
|
||||||
|
with open(f"{md.title}.md", "w") as md_file:
|
||||||
|
md_file.write(md.content)
|
||||||
|
with open(f"{md.title}.mdmeta", "w") as meta_file:
|
||||||
|
meta_file.write(f"created_at: {md.created_at}\n")
|
||||||
|
meta_file.write(f"order: {md.order}\n")
|
||||||
|
children = [c for c in paths.values() if c.parent_id == path_id]
|
||||||
|
for child in children:
|
||||||
|
traverse(child.id, paths)
|
||||||
|
cd_back()
|
||||||
4
app.py
4
app.py
@@ -32,7 +32,9 @@ CORS(app, resources={r"/api/*": {"origins": [
|
|||||||
r"https?://localhost:\d+",
|
r"https?://localhost:\d+",
|
||||||
r"https?://127\.0\.0\.1:\d+",
|
r"https?://127\.0\.0\.1:\d+",
|
||||||
r"https?://localhost"
|
r"https?://localhost"
|
||||||
]}})
|
]}},
|
||||||
|
expose_headers=['Content-Disposition']
|
||||||
|
)
|
||||||
|
|
||||||
limiter.init_app(app)
|
limiter.init_app(app)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user