Files page: thumbnails + view + download (email access to finished work)
This commit is contained in:
@@ -23,7 +23,7 @@ from typing import Annotated
|
||||
|
||||
from fastapi import Depends, FastAPI, File, Form, HTTPException, Request, UploadFile
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse, Response
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse, Response, Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from sqlalchemy import select
|
||||
@@ -237,10 +237,55 @@ def _shared_files(username: str) -> list:
|
||||
out = []
|
||||
for f in sorted(user_dir.iterdir()):
|
||||
if f.is_file():
|
||||
out.append({"name": f.name, "size": f.stat().st_size})
|
||||
out.append({"name": f.name, "size": f.stat().st_size,
|
||||
"img": f.suffix.lower() in _IMAGE_MIME})
|
||||
return out
|
||||
|
||||
|
||||
|
||||
_IMAGE_MIME = {".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
|
||||
".gif": "image/gif", ".webp": "image/webp", ".bmp": "image/bmp",
|
||||
".svg": "image/svg+xml"}
|
||||
|
||||
|
||||
def _resolve_shared(username: str, name: str):
|
||||
"""Return a validated path inside this user's shared folder, or None."""
|
||||
if Path(name).name != name: # block path traversal
|
||||
return None
|
||||
p = (SHARED_DIR / username / name).resolve()
|
||||
base = (SHARED_DIR / username).resolve()
|
||||
if not str(p).startswith(str(base)):
|
||||
return None
|
||||
return p if p.is_file() else None
|
||||
|
||||
|
||||
@app.get("/files/raw/{name}")
|
||||
async def files_raw(request: Request, name: str) -> Response:
|
||||
"""Inline the file (images render as a preview)."""
|
||||
user = await _current_user(request)
|
||||
if user is None:
|
||||
return RedirectResponse("/login", status_code=303)
|
||||
p = _resolve_shared(user.username, name)
|
||||
if p is None:
|
||||
return Response(b"Not found", status_code=404)
|
||||
media = _IMAGE_MIME.get(p.suffix.lower(), "application/octet-stream")
|
||||
return Response(content=p.read_bytes(), media_type=media)
|
||||
|
||||
|
||||
@app.get("/files/dl/{name}")
|
||||
async def files_dl(request: Request, name: str) -> Response:
|
||||
"""Download (forced attachment) a file so it can be saved/emailed."""
|
||||
user = await _current_user(request)
|
||||
if user is None:
|
||||
return RedirectResponse("/login", status_code=303)
|
||||
p = _resolve_shared(user.username, name)
|
||||
if p is None:
|
||||
return Response(b"Not found", status_code=404)
|
||||
media = _IMAGE_MIME.get(p.suffix.lower(), "application/octet-stream")
|
||||
return Response(content=p.read_bytes(), media_type=media,
|
||||
headers={"Content-Disposition": f'attachment; filename="{p.name}"'})
|
||||
|
||||
|
||||
@app.get("/files", response_class=HTMLResponse)
|
||||
async def files_page(request: Request) -> HTMLResponse:
|
||||
user = await _current_user(request)
|
||||
|
||||
Reference in New Issue
Block a user