from flask import Blueprint, jsonify, request, abort from flask_login import login_required, current_user from app.extensions import db from app.models.folder import Folder from app.utils.file_handler import delete_resource_file folders_bp = Blueprint('folders', __name__) # ── 递归删除辅助函数 ────────────────────────────────────────────────────────── def _delete_folder_recursive(folder: Folder): """DFS 递归删除文件夹:先删子文件夹,再删资源(含磁盘文件),最后删自身""" for child in list(folder.children): _delete_folder_recursive(child) for res in folder.resources.all(): delete_resource_file(res.file_path) db.session.delete(res) db.session.delete(folder) # ── 路由 ────────────────────────────────────────────────────────────────────── @folders_bp.route('/tree') @login_required def tree(): """返回当前用户的完整文件夹树(JSON)""" return jsonify(Folder.get_user_tree(current_user.id)) @folders_bp.route('/create', methods=['POST']) @login_required def create(): """创建文件夹""" name = request.form.get('name', '').strip() parent_id_raw = request.form.get('parent_id', '').strip() parent_id = int(parent_id_raw) if parent_id_raw.isdigit() else None if not name or len(name) > 128: return jsonify(ok=False, msg='文件夹名称无效(1-128位)'), 400 # 验证父文件夹归属 if parent_id is not None: parent = db.session.get(Folder, parent_id) if parent is None or parent.user_id != current_user.id: abort(403) # 同级重名校验(覆盖 NULL parent_id 场景) dup = Folder.query.filter_by( user_id=current_user.id, parent_id=parent_id, name=name ).first() if dup: return jsonify(ok=False, msg='该位置已存在同名文件夹'), 409 folder = Folder(user_id=current_user.id, name=name, parent_id=parent_id) db.session.add(folder) db.session.commit() return jsonify(ok=True, folder={ 'id': folder.id, 'name': folder.name, 'parent_id': folder.parent_id, 'resource_count': 0, 'children': [] }) @folders_bp.route('//rename', methods=['POST']) @login_required def rename(folder_id): """重命名文件夹""" folder = db.session.get(Folder, folder_id) if folder is None or folder.user_id != current_user.id: abort(403) name = request.form.get('name', '').strip() if not name or len(name) > 128: return jsonify(ok=False, msg='文件夹名称无效'), 400 if name == folder.name: return jsonify(ok=True) # 同级重名校验 dup = Folder.query.filter_by( user_id=current_user.id, parent_id=folder.parent_id, name=name ).filter(Folder.id != folder_id).first() if dup: return jsonify(ok=False, msg='该位置已存在同名文件夹'), 409 folder.name = name db.session.commit() return jsonify(ok=True) @folders_bp.route('//delete', methods=['POST']) @login_required def delete(folder_id): """递归删除文件夹(含所有子文件夹和资源)""" folder = db.session.get(Folder, folder_id) if folder is None or folder.user_id != current_user.id: abort(403) _delete_folder_recursive(folder) db.session.commit() return jsonify(ok=True) @folders_bp.route('//move', methods=['POST']) @login_required def move(folder_id): """移动文件夹到新的父文件夹""" folder = db.session.get(Folder, folder_id) if folder is None or folder.user_id != current_user.id: abort(403) parent_id_raw = request.form.get('parent_id', '').strip() new_parent_id = int(parent_id_raw) if parent_id_raw.isdigit() else None if new_parent_id is not None: # 不能移动到自身 if new_parent_id == folder_id: return jsonify(ok=False, msg='不能将文件夹移动到自身'), 400 # 不能移动到自己的子孙节点 if folder.is_ancestor_of(new_parent_id): return jsonify(ok=False, msg='不能将文件夹移动到其子文件夹中'), 400 # 验证目标父文件夹归属 new_parent = db.session.get(Folder, new_parent_id) if new_parent is None or new_parent.user_id != current_user.id: abort(403) # 同级重名校验 dup = Folder.query.filter_by( user_id=current_user.id, parent_id=new_parent_id, name=folder.name ).filter(Folder.id != folder_id).first() if dup: return jsonify(ok=False, msg='目标位置已有同名文件夹'), 409 folder.parent_id = new_parent_id db.session.commit() return jsonify(ok=True)