190 lines
5.3 KiB
Python
190 lines
5.3 KiB
Python
import csv
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, File, HTTPException, UploadFile
|
|
from fastapi.responses import FileResponse
|
|
from pydantic import BaseModel
|
|
|
|
from tools.doc_generator.logic.document_types import get_document_type, list_document_types
|
|
from tools.doc_generator.logic.renderer import generate_docx
|
|
|
|
router = APIRouter()
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
EXPORTS_DIR = PROJECT_ROOT / "exports"
|
|
INPUTS_DIR = PROJECT_ROOT / "inputs"
|
|
UPLOADS_DIR = INPUTS_DIR / "uploads"
|
|
JSON_UPLOADS_DIR = UPLOADS_DIR / "json"
|
|
DATA_UPLOADS_DIR = UPLOADS_DIR / "data"
|
|
|
|
|
|
class GenerateDocRequest(BaseModel):
|
|
document_type_id: str
|
|
data: dict
|
|
|
|
|
|
def safe_filename(filename: str) -> str:
|
|
filename = Path(filename or "upload").name
|
|
filename = re.sub(r"[^A-Za-z0-9._ -]+", "", filename)
|
|
filename = re.sub(r"\s+", "_", filename).strip("._ ")
|
|
return filename or "upload"
|
|
|
|
|
|
def save_upload(upload: UploadFile, directory: Path) -> Path:
|
|
directory.mkdir(parents=True, exist_ok=True)
|
|
filename = safe_filename(upload.filename)
|
|
path = directory / filename
|
|
|
|
counter = 1
|
|
while path.exists():
|
|
stem = path.stem
|
|
suffix = path.suffix
|
|
path = directory / f"{stem}_{counter}{suffix}"
|
|
counter += 1
|
|
|
|
with path.open("wb") as f:
|
|
f.write(upload.file.read())
|
|
|
|
return path
|
|
|
|
|
|
@router.get("/document-types")
|
|
def document_types():
|
|
return {"document_types": list_document_types()}
|
|
|
|
|
|
@router.get("/document-types/{document_type_id}")
|
|
def document_type(document_type_id: str):
|
|
try:
|
|
return get_document_type(document_type_id)
|
|
except FileNotFoundError as exc:
|
|
raise HTTPException(status_code=404, detail=str(exc))
|
|
|
|
|
|
@router.post("/upload-json")
|
|
async def upload_json(file: UploadFile = File(...)):
|
|
if not file.filename.lower().endswith(".json"):
|
|
raise HTTPException(status_code=400, detail="Upload must be a JSON file.")
|
|
|
|
path = save_upload(file, JSON_UPLOADS_DIR)
|
|
|
|
try:
|
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=400, detail=f"Invalid JSON: {exc}")
|
|
|
|
return {
|
|
"ok": True,
|
|
"filename": path.name,
|
|
"saved_path": str(path.relative_to(PROJECT_ROOT)),
|
|
"json": data,
|
|
}
|
|
|
|
|
|
@router.get("/uploaded-json")
|
|
def uploaded_json_files():
|
|
JSON_UPLOADS_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
files = []
|
|
for path in sorted(JSON_UPLOADS_DIR.glob("*.json")):
|
|
files.append({
|
|
"filename": path.name,
|
|
"saved_path": str(path.relative_to(PROJECT_ROOT)),
|
|
"modified": path.stat().st_mtime,
|
|
})
|
|
|
|
return {"files": files}
|
|
|
|
|
|
@router.get("/uploaded-json/{filename}")
|
|
def get_uploaded_json(filename: str):
|
|
file_path = JSON_UPLOADS_DIR / safe_filename(filename)
|
|
|
|
if not file_path.exists():
|
|
raise HTTPException(status_code=404, detail="Uploaded JSON not found.")
|
|
|
|
try:
|
|
data = json.loads(file_path.read_text(encoding="utf-8"))
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=400, detail=f"Invalid JSON: {exc}")
|
|
|
|
return {
|
|
"ok": True,
|
|
"filename": file_path.name,
|
|
"saved_path": str(file_path.relative_to(PROJECT_ROOT)),
|
|
"json": data,
|
|
}
|
|
|
|
|
|
@router.delete("/uploaded-json/{filename}")
|
|
def delete_uploaded_json(filename: str):
|
|
file_path = JSON_UPLOADS_DIR / safe_filename(filename)
|
|
|
|
if not file_path.exists():
|
|
raise HTTPException(status_code=404, detail="Uploaded JSON not found.")
|
|
|
|
file_path.unlink()
|
|
|
|
return {
|
|
"ok": True,
|
|
"deleted": file_path.name,
|
|
}
|
|
|
|
|
|
@router.post("/upload-data")
|
|
async def upload_data(file: UploadFile = File(...)):
|
|
if not file.filename.lower().endswith(".csv"):
|
|
raise HTTPException(status_code=400, detail="For now, upload data as CSV.")
|
|
|
|
path = save_upload(file, DATA_UPLOADS_DIR)
|
|
|
|
try:
|
|
with path.open("r", encoding="utf-8-sig", newline="") as f:
|
|
reader = csv.DictReader(f)
|
|
rows = list(reader)
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=400, detail=f"Could not read CSV: {exc}")
|
|
|
|
if not rows:
|
|
raise HTTPException(status_code=400, detail="CSV has no data rows.")
|
|
|
|
return {
|
|
"ok": True,
|
|
"filename": path.name,
|
|
"saved_path": str(path.relative_to(PROJECT_ROOT)),
|
|
"data": rows[0],
|
|
"row_count": len(rows),
|
|
}
|
|
|
|
|
|
@router.post("/generate")
|
|
def generate_document(request: GenerateDocRequest):
|
|
try:
|
|
output_path = generate_docx(request.document_type_id, request.data)
|
|
except FileNotFoundError as exc:
|
|
raise HTTPException(status_code=404, detail=str(exc))
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc))
|
|
|
|
return {
|
|
"ok": True,
|
|
"filename": output_path.name,
|
|
"download_url": f"/api/doc-generator/download/{output_path.name}"
|
|
}
|
|
|
|
|
|
@router.get("/download/{filename}")
|
|
def download(filename: str):
|
|
file_path = EXPORTS_DIR / safe_filename(filename)
|
|
|
|
if not file_path.exists():
|
|
raise HTTPException(status_code=404, detail="File not found")
|
|
|
|
return FileResponse(
|
|
path=file_path,
|
|
filename=file_path.name,
|
|
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
)
|