From bef57027f2e60a7ee1f0803356754fb788ad251f Mon Sep 17 00:00:00 2001 From: Martin Hohenberg Date: Thu, 18 Jun 2026 22:52:26 +0200 Subject: [PATCH] Add file download for job models - GET /files/{filename}: serves file from STL_UPLOAD_DIR with path-traversal guard - Download button in File & Geometry card header (always visible when file attached) - Download button also appears at the top of the Start Job modal Co-Authored-By: Claude Sonnet 4.6 --- app/routers/ui.py | 14 +++++++++++++- app/templates/job_detail.html | 26 ++++++++++++++++++++------ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/app/routers/ui.py b/app/routers/ui.py index b325139..d8ae935 100644 --- a/app/routers/ui.py +++ b/app/routers/ui.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta from pathlib import Path from fastapi import APIRouter, Depends, File, Form, Request, UploadFile -from fastapi.responses import HTMLResponse, RedirectResponse +from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from sqlalchemy import case, or_ from sqlalchemy.orm import Session @@ -297,6 +297,18 @@ def edit_job( return RedirectResponse(url=f"/jobs/{job_id}", status_code=HTTP_303_SEE_OTHER) +@router.get("/files/{filename}") +def download_file(filename: str): + from fastapi import HTTPException + stl_dir = get_stl_dir() + path = (stl_dir / filename).resolve() + if not str(path).startswith(str(stl_dir.resolve())): + raise HTTPException(400, "Invalid filename") + if not path.exists(): + raise HTTPException(404, "File not found") + return FileResponse(path, filename=filename, media_type="application/octet-stream") + + @router.post("/jobs/{job_id}/start") def start_job(job_id: int, printer_id: int = Form(...), db: Session = Depends(get_db)): from fastapi import HTTPException diff --git a/app/templates/job_detail.html b/app/templates/job_detail.html index 3ea8280..177efa2 100644 --- a/app/templates/job_detail.html +++ b/app/templates/job_detail.html @@ -98,12 +98,19 @@
File & Geometry - {% if job.file_name and not job.file_name.endswith('.gcode') %} -
- -
+ {% if job.file_name %} +
+ + Download + + {% if not job.file_name.endswith('.gcode') %} +
+ +
+ {% endif %} +
{% endif %}
@@ -350,6 +357,13 @@