| import os
|
| import io
|
| import json
|
| import shutil
|
|
|
| import sqlite3
|
|
|
| from pathlib import Path
|
|
|
| from fastapi import APIRouter, UploadFile, File, Query, HTTPException
|
| from fastapi.responses import FileResponse, JSONResponse
|
|
|
|
|
| from storage.files.file_manager import FileManager
|
| from storage.common import validate_token
|
|
|
| router = APIRouter(prefix="/data", tags=["Data Manager"])
|
| DATA_ROOT = Path("/data")
|
| HF_TOKEN = os.getenv("HF_TOKEN")
|
|
|
|
|
| @router.get("/data_tree", tags=["Data Manager"])
|
| def get_data_tree(
|
| token: str = Query(..., description="Token required for authorization")
|
| ):
|
| """
|
| Return a formatted tree of folders and files under /data.
|
|
|
| Behavior:
|
| - Validate token.
|
| - Walk the /data directory and build a recursive tree.
|
| - Each entry includes: name, type (file/directory), and children if directory.
|
| """
|
|
|
| validate_token(token)
|
|
|
| def build_tree(path: Path):
|
| """
|
| Build a tree representation of directories and files.
|
| Prevents errors by checking if the path is a directory before iterating.
|
| """
|
|
|
| if not path.exists():
|
| return {"name": path.name, "type": "missing"}
|
|
|
|
|
| if path.is_file():
|
| return {
|
| "name": path.name,
|
| "type": "file"
|
| }
|
|
|
|
|
| children = []
|
| try:
|
| entries = sorted(path.iterdir(), key=lambda p: p.name.lower())
|
| except Exception:
|
|
|
| return {
|
| "name": path.name,
|
| "type": "file"
|
| }
|
|
|
| for child in entries:
|
| children.append(build_tree(child))
|
|
|
| return {
|
| "name": path.name,
|
| "type": "directory",
|
| "children": children
|
| }
|
|
|
| if not DATA_ROOT.exists():
|
| return {"error": "/data does not exist"}
|
|
|
| return build_tree(DATA_ROOT)
|
|
|
| @router.post("/create_data_item", tags=["Data Manager"])
|
| def create_data_item(
|
| path: str = Query(..., description="Full path starting with /data/"),
|
| item_type: str = Query(..., description="directory or file"),
|
| token: str = Query(..., description="Token required for authorization")
|
| ):
|
| """
|
| Create a directory or file under /data.
|
|
|
| Restrictions:
|
| - Path must start with /data/.
|
| - Writing to /data/db or /data/media (or any of their subpaths) is forbidden.
|
| - item_type must be 'directory' or 'file'.
|
|
|
| Behavior:
|
| - Validate token.
|
| - Check path validity and protection.
|
| - Create directory or empty file.
|
| - Raise error if path already exists.
|
| """
|
|
|
| validate_token(token)
|
|
|
| target = Path(path)
|
|
|
|
|
| if not path.startswith("/data/"):
|
| raise HTTPException(status_code=400, detail="Path must start with /data/")
|
|
|
| if item_type not in ("directory", "file"):
|
| raise HTTPException(status_code=400, detail="item_type must be 'directory' or 'file'")
|
|
|
|
|
| protected = ["/data/db", "/data/media"]
|
|
|
| for p in protected:
|
| if path == p or path.startswith(p + "/"):
|
| raise HTTPException(
|
| status_code=403,
|
| detail=f"Access to protected path '{p}' is not allowed"
|
| )
|
|
|
|
|
| if target.exists():
|
| raise HTTPException(status_code=409, detail="Path already exists")
|
|
|
| try:
|
| if item_type == "directory":
|
| target.mkdir(parents=True, exist_ok=False)
|
| else:
|
| target.parent.mkdir(parents=True, exist_ok=True)
|
| with open(target, "wb") as f:
|
| f.write(b"")
|
| except Exception as exc:
|
| raise HTTPException(status_code=500, detail=f"Failed to create item: {exc}")
|
|
|
| return {
|
| "status": "ok",
|
| "created": str(target),
|
| "type": item_type
|
| }
|
|
|
| @router.delete("/delete_path", tags=["Data Manager"])
|
| def delete_path(
|
| target: str = Query(..., description="Ruta absoluta dentro de /data a borrar"),
|
| token: str = Query(..., description="Token requerido para autorización")
|
| ):
|
| """
|
| Delete a file or directory recursively inside /data, except protected folders.
|
|
|
| Behavior:
|
| - Validate token.
|
| - Validate path starts with /data/.
|
| - Deny deletion of /data/db and /data/media (and anything inside them).
|
| - If target is a file → delete file.
|
| - If target is a directory → delete recursively.
|
| """
|
|
|
| validate_token(token)
|
|
|
|
|
| path = Path(target).resolve()
|
|
|
|
|
| if not str(path).startswith(str(DATA_ROOT)):
|
| raise HTTPException(status_code=400, detail="Path must be inside /data/")
|
|
|
|
|
| protected = [
|
| DATA_ROOT / "db",
|
| DATA_ROOT / "media",
|
| ]
|
|
|
| for p in protected:
|
| if path == p or str(path).startswith(str(p) + "/"):
|
| raise HTTPException(
|
| status_code=403,
|
| detail=f"Deletion forbidden: {p} is protected"
|
| )
|
|
|
|
|
| if not path.exists():
|
| raise HTTPException(status_code=404, detail="Target path does not exist")
|
|
|
| try:
|
| if path.is_file():
|
| path.unlink()
|
| else:
|
| shutil.rmtree(path)
|
|
|
| except Exception as exc:
|
| raise HTTPException(status_code=500, detail=f"Failed to delete: {exc}")
|
|
|
| return {
|
| "status": "ok",
|
| "deleted": str(path)
|
| } |