cc3m / routers /files.py
kokokoasd's picture
Upload 21 files
36ce73b verified
"""
File management API.
Single Responsibility: only handles file CRUD operations within zones.
Separated from zone management (zones.py) — each has its own reason to change.
"""
import os
import shutil
from datetime import datetime
from pathlib import Path
from fastapi import APIRouter, Depends, Form, File, UploadFile, Query, HTTPException
from fastapi.responses import FileResponse
from auth import AuthUser, get_current_user
from storage import get_zone_path, safe_path, check_zone_owner
router = APIRouter(prefix="/api/zones/{zone_name}/files", tags=["files"])
def _check_access(zone_name: str, user: AuthUser):
"""Validate zone access for the current user."""
check_zone_owner(zone_name, user.sub, user.role)
@router.get("")
def list_files(zone_name: str, path: str = Query(""), user: AuthUser = Depends(get_current_user)):
try:
_check_access(zone_name, user)
zone_path = get_zone_path(zone_name)
target = safe_path(zone_path, path)
if not target.is_dir():
raise ValueError("Không phải thư mục")
return [
{
"name": item.name,
"is_dir": item.is_dir(),
"size": item.stat().st_size if item.is_file() else 0,
"modified": datetime.fromtimestamp(item.stat().st_mtime).isoformat(),
}
for item in sorted(target.iterdir())
]
except ValueError as e:
raise HTTPException(400, str(e))
@router.get("/read")
def read_file(zone_name: str, path: str = Query(...), user: AuthUser = Depends(get_current_user)):
try:
_check_access(zone_name, user)
zone_path = get_zone_path(zone_name)
target = safe_path(zone_path, path)
if not target.is_file():
raise ValueError("File không tồn tại")
return {"content": target.read_text(encoding="utf-8", errors="replace"), "path": path}
except ValueError as e:
raise HTTPException(400, str(e))
@router.get("/download")
def download_file(zone_name: str, path: str = Query(...), user: AuthUser = Depends(get_current_user)):
try:
_check_access(zone_name, user)
zone_path = get_zone_path(zone_name)
target = safe_path(zone_path, path)
if not target.is_file():
raise HTTPException(404, "File không tồn tại")
return FileResponse(target, filename=target.name)
except ValueError as e:
raise HTTPException(400, str(e))
@router.post("/write")
def write_file(zone_name: str, path: str = Form(...), content: str = Form(...), user: AuthUser = Depends(get_current_user)):
try:
_check_access(zone_name, user)
zone_path = get_zone_path(zone_name)
target = safe_path(zone_path, path)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content, encoding="utf-8")
return {"ok": True}
except ValueError as e:
raise HTTPException(400, str(e))
@router.post("/mkdir")
def create_folder(zone_name: str, path: str = Form(...), user: AuthUser = Depends(get_current_user)):
try:
_check_access(zone_name, user)
zone_path = get_zone_path(zone_name)
target = safe_path(zone_path, path)
target.mkdir(parents=True, exist_ok=True)
return {"ok": True}
except ValueError as e:
raise HTTPException(400, str(e))
@router.post("/upload")
async def upload_file(zone_name: str, path: str = Form(""), file: UploadFile = File(...), user: AuthUser = Depends(get_current_user)):
try:
_check_access(zone_name, user)
zone_path = get_zone_path(zone_name)
dest = safe_path(zone_path, os.path.join(path, file.filename))
dest.parent.mkdir(parents=True, exist_ok=True)
content = await file.read()
dest.write_bytes(content)
return {"ok": True, "path": str(dest.relative_to(zone_path))}
except ValueError as e:
raise HTTPException(400, str(e))
@router.delete("")
def delete_file(zone_name: str, path: str = Query(...), user: AuthUser = Depends(get_current_user)):
try:
_check_access(zone_name, user)
zone_path = get_zone_path(zone_name)
target = safe_path(zone_path, path)
if target == zone_path.resolve():
raise ValueError("Không thể xoá thư mục gốc zone")
if target.is_dir():
shutil.rmtree(target)
elif target.is_file():
target.unlink()
else:
raise ValueError("File/thư mục không tồn tại")
return {"ok": True}
except ValueError as e:
raise HTTPException(400, str(e))
@router.post("/rename")
def rename_file(zone_name: str, old_path: str = Form(...), new_name: str = Form(...), user: AuthUser = Depends(get_current_user)):
try:
_check_access(zone_name, user)
zone_path = get_zone_path(zone_name)
source = safe_path(zone_path, old_path)
if not source.exists():
raise ValueError("File/thư mục nguồn không tồn tại")
dest = safe_path(zone_path, str(Path(old_path).parent / new_name))
source.rename(dest)
return {"ok": True}
except ValueError as e:
raise HTTPException(400, str(e))