feat(admin): upload-material + delete-material endpoints with whitelist
This commit is contained in:
40
app/admin.py
40
app/admin.py
@@ -2,11 +2,12 @@ import csv
|
|||||||
import io
|
import io
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, UploadFile
|
||||||
from fastapi.responses import Response
|
from fastapi.responses import Response
|
||||||
|
|
||||||
from app.config import get_settings
|
from app.config import get_settings
|
||||||
from app.db import list_leads, stats
|
from app.db import list_leads, stats
|
||||||
|
from app.frontend import ALLOWED_FILENAMES
|
||||||
from app.security import require_admin
|
from app.security import require_admin
|
||||||
|
|
||||||
router = APIRouter(prefix="/admin", tags=["admin"])
|
router = APIRouter(prefix="/admin", tags=["admin"])
|
||||||
@@ -30,6 +31,43 @@ def admin_material_files(_user: str = Depends(require_admin)):
|
|||||||
return {"material_dir": str(material_dir), "exists": True, "files": files}
|
return {"material_dir": str(material_dir), "exists": True, "files": files}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/upload-material")
|
||||||
|
async def admin_upload_material(
|
||||||
|
target_name: str = Form(...),
|
||||||
|
file: UploadFile = File(...),
|
||||||
|
_user: str = Depends(require_admin),
|
||||||
|
):
|
||||||
|
"""Sube un archivo al volumen /app/material/ con un nombre de la whitelist."""
|
||||||
|
if target_name not in ALLOWED_FILENAMES:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"target_name must be one of {sorted(ALLOWED_FILENAMES)}",
|
||||||
|
)
|
||||||
|
material_dir = Path(get_settings().material_dir)
|
||||||
|
material_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
dest = material_dir / target_name
|
||||||
|
content = await file.read()
|
||||||
|
dest.write_bytes(content)
|
||||||
|
return {
|
||||||
|
"saved_as": str(dest),
|
||||||
|
"size_bytes": len(content),
|
||||||
|
"size_mb": round(len(content) / (1024 * 1024), 2),
|
||||||
|
"original_filename": file.filename,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/material-files/{filename}")
|
||||||
|
def admin_delete_material(filename: str, _user: str = Depends(require_admin)):
|
||||||
|
"""Borra un archivo del volumen material/."""
|
||||||
|
if filename not in ALLOWED_FILENAMES:
|
||||||
|
raise HTTPException(status_code=400, detail="filename not in whitelist")
|
||||||
|
path = Path(get_settings().material_dir) / filename
|
||||||
|
if not path.exists():
|
||||||
|
raise HTTPException(status_code=404, detail="file not found")
|
||||||
|
path.unlink()
|
||||||
|
return {"deleted": filename}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/leads.json")
|
@router.get("/leads.json")
|
||||||
def admin_leads_json(
|
def admin_leads_json(
|
||||||
_user: str = Depends(require_admin),
|
_user: str = Depends(require_admin),
|
||||||
|
|||||||
Reference in New Issue
Block a user