Files page: thumbnails + view + download (email access to finished work)

This commit is contained in:
2026-08-26 15:30:15 +10:00
parent 1cc159ef80
commit 188e9a0dd6
2 changed files with 58 additions and 3 deletions

View File

@@ -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)