triflix commited on
Commit
3fcdea6
·
verified ·
1 Parent(s): 788bfc4

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -0
app.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import aiofiles # For async file operations, good practice with FastAPI
4
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Request
5
+ from fastapi.responses import HTMLResponse, FileResponse, JSONResponse
6
+ from fastapi.staticfiles import StaticFiles
7
+ from fastapi.templating import Jinja2Templates
8
+ from pathlib import Path
9
+
10
+ # --- Configuration ---
11
+ BASE_DIR = Path(__file__).resolve().parent
12
+ FILES_DIR = BASE_DIR / "uploaded_files"
13
+ STATIC_DIR = BASE_DIR / "static"
14
+ TEMPLATES_DIR = BASE_DIR / "templates"
15
+
16
+ # Create directories if they don't exist
17
+ FILES_DIR.mkdir(parents=True, exist_ok=True)
18
+ STATIC_DIR.mkdir(parents=True, exist_ok=True) # For CSS/JS if not using CDN
19
+ TEMPLATES_DIR.mkdir(parents=True, exist_ok=True)
20
+
21
+ app = FastAPI(title="File Uploader/Downloader")
22
+
23
+ # Mount static files (if you have local CSS/JS)
24
+ # app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
25
+
26
+ # Setup Jinja2 templates
27
+ templates = Jinja2Templates(directory=TEMPLATES_DIR)
28
+
29
+ # --- Helper Functions ---
30
+ def sanitize_filename(filename: str) -> str:
31
+ """Basic filename sanitization."""
32
+ return os.path.basename(filename).strip()
33
+
34
+ # --- Routes ---
35
+ @app.get("/", response_class=HTMLResponse)
36
+ async def read_root(request: Request):
37
+ """Serves the main HTML page."""
38
+ return templates.TemplateResponse("index.html", {"request": request})
39
+
40
+ @app.post("/upload/")
41
+ async def upload_file(file: UploadFile = File(...)):
42
+ """Handles file uploads."""
43
+ original_filename = file.filename
44
+ if not original_filename:
45
+ raise HTTPException(status_code=400, detail="No filename provided.")
46
+
47
+ filename = sanitize_filename(original_filename)
48
+ if not filename: # After sanitization, if it's empty
49
+ raise HTTPException(status_code=400, detail="Invalid filename after sanitization.")
50
+
51
+ file_path = FILES_DIR / filename
52
+
53
+ # Security: Prevent writing outside the designated FILES_DIR
54
+ # (Pathlib helps, but an explicit check is good for clarity/robustness)
55
+ if not file_path.resolve().is_relative_to(FILES_DIR.resolve()):
56
+ raise HTTPException(status_code=400, detail="Invalid file path (attempted directory traversal).")
57
+
58
+ try:
59
+ # Stream the file to disk in chunks to handle large files efficiently
60
+ # and avoid loading the whole file into memory at once.
61
+ async with aiofiles.open(file_path, "wb") as buffer:
62
+ while content := await file.read(1024 * 1024): # Read 1MB chunks
63
+ await buffer.write(content)
64
+ except Exception as e:
65
+ # Clean up partially uploaded file on error
66
+ if file_path.exists():
67
+ file_path.unlink()
68
+ raise HTTPException(status_code=500, detail=f"Could not save file: {str(e)}")
69
+
70
+ return JSONResponse(
71
+ content={
72
+ "message": "File uploaded successfully",
73
+ "filename": filename,
74
+ "download_url": f"/download/{filename}" # Relative URL
75
+ }
76
+ )
77
+
78
+ @app.get("/download/{filename}")
79
+ async def download_file(filename: str):
80
+ """Handles file downloads."""
81
+ clean_filename = sanitize_filename(filename)
82
+ if not clean_filename:
83
+ raise HTTPException(status_code=400, detail="Invalid filename.")
84
+
85
+ file_path = FILES_DIR / clean_filename
86
+
87
+ # Security: Ensure the file is within the FILES_DIR and exists
88
+ if not file_path.resolve().is_relative_to(FILES_DIR.resolve()) or not file_path.is_file():
89
+ raise HTTPException(status_code=404, detail="File not found or access denied.")
90
+
91
+ return FileResponse(path=file_path, filename=clean_filename, media_type='application/octet-stream')
92
+
93
+ if __name__ == "__main__":
94
+ import uvicorn
95
+ # This part is for local execution, not used by Docker CMD
96
+ uvicorn.run(app, host="0.0.0.0", port=7860)