diff --git a/backend/app/main.py b/backend/app/main.py index 9b41e80..409e0d8 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -4,7 +4,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.config import settings -from app.routers import documents, search +from app.routers import documents, files, search logger = logging.getLogger(__name__) @@ -19,6 +19,7 @@ app.add_middleware( ) app.include_router(documents.router) +app.include_router(files.router) app.include_router(search.router) diff --git a/backend/app/routers/files.py b/backend/app/routers/files.py new file mode 100644 index 0000000..a816f7a --- /dev/null +++ b/backend/app/routers/files.py @@ -0,0 +1,26 @@ +import mimetypes +from pathlib import Path + +from fastapi import APIRouter, HTTPException +from fastapi.responses import FileResponse + +from app.config import settings + +router = APIRouter() + + +def _resolve_safe(key: str) -> Path: + base = Path(settings.storage_dir).resolve() + path = (base / key).resolve() + if not path.is_relative_to(base): + raise HTTPException(status_code=400, detail="Invalid path") + return path + + +@router.get("/api/files/{key:path}") +def serve_file(key: str): + path = _resolve_safe(key) + if not path.exists() or not path.is_file(): + raise HTTPException(status_code=404, detail="File not found") + media_type, _ = mimetypes.guess_type(str(path)) + return FileResponse(path, media_type=media_type or "application/octet-stream") diff --git a/frontend/src/components/DocumentEditor.tsx b/frontend/src/components/DocumentEditor.tsx index 893970a..7db9df7 100644 --- a/frontend/src/components/DocumentEditor.tsx +++ b/frontend/src/components/DocumentEditor.tsx @@ -25,6 +25,7 @@ export default function DocumentEditor({ documentKey, onDirtyChange, onDeleted } const [isPreview, setIsPreview] = useState(true); const isImage = contentType.startsWith('image/'); + const isHtml = contentType === 'text/html'; const isMarkdown = contentType === 'text/markdown'; const extensions = useMemo(() => [isMarkdown ? markdown() : html()], [isMarkdown]); @@ -125,7 +126,7 @@ export default function DocumentEditor({ documentKey, onDirtyChange, onDeleted } - {!isImage && ( + {!isImage && !isHtml && ( <>