feat(storage): add S3 image storage support with file-based config and migration script

This commit is contained in:
2026-07-18 08:03:18 +08:00
parent ddeec36a01
commit 3a931fe752
11 changed files with 242 additions and 30 deletions

View File

@@ -13,7 +13,7 @@ from app.models import (
PERM_POSTCARDS_READ, PERM_POSTCARDS_WRITE,
PERM_IMAGES_UPLOAD,
)
from app.config import UPLOAD_DIR
# config removed: image storage now via app.storage
router = APIRouter(prefix="/api/v1", tags=["api"])
@@ -380,15 +380,16 @@ async def upload_image(
ak: ApiKey = Depends(require_scopes(PERM_IMAGES_UPLOAD)),
):
pc = _postcard_or_404(ak.user, profile_id, postcard_id, db)
from app.storage import get_storage
storage = get_storage()
ext = Path(file.filename).suffix if file.filename else ".jpg"
save_name = f"pc_{pc.id}_{side}{ext}"
save_path = UPLOAD_DIR / save_name
key = f"pc_{pc.id}_{side}{ext}"
content = await file.read()
save_path.write_bytes(content)
storage.put(key, content)
if side == "front":
pc.image_front = f"/uploads/{save_name}"
pc.image_front = key
else:
pc.image_back = f"/uploads/{save_name}"
pc.image_back = key
db.commit()
db.refresh(pc)
return pc

View File

@@ -16,7 +16,7 @@ from app.auth import (
redirect_if_not_logged_in,
verify_password,
)
from app.config import SECRET_KEY, SESSION_COOKIE_NAME, UPLOAD_DIR
from app.config import SECRET_KEY, SESSION_COOKIE_NAME
from app.database import get_db
from random import choice
from string import ascii_letters, digits
@@ -86,6 +86,10 @@ def _translate_filter(key: str, lang: str = "zh") -> str:
templates.env.filters["t"] = _translate_filter
# image URL resolver: bare key → full URL (local /uploads/... or S3 public URL)
from app.storage import resolve_url as _resolve_image_url
templates.env.filters["image_url"] = lambda v: _resolve_image_url(v) if v else ""
from app.i18n import get_languages as _get_languages
templates.env.globals["available_languages"] = _get_languages()
@@ -188,7 +192,7 @@ def api_public_cards(
"page": page,
"has_more": start + limit < total,
"cards": [{
"image_front": c.image_front,
"image_front": _resolve_image_url(c.image_front),
"country_from": c.country_from,
"country_to": c.country_to,
"status": c.status,
@@ -230,7 +234,7 @@ def api_public_cards_user(
"page": page,
"has_more": start + limit < total,
"cards": [{
"image_front": c.image_front,
"image_front": _resolve_image_url(c.image_front),
"country_from": c.country_from,
"country_to": c.country_to,
} for c in batch],
@@ -959,12 +963,13 @@ async def upload_image(
)
if not pc:
return _redirect("/profiles")
from app.storage import get_storage
storage = get_storage()
ext = Path(file.filename or "upload.jpg").suffix or ".jpg"
filename = f"{uuid4().hex}{ext}"
dest = UPLOAD_DIR / filename
key = f"{uuid4().hex}{ext}"
content = await file.read()
dest.write_bytes(content)
setattr(pc, f"image_{side}", f"/uploads/{filename}")
storage.put(key, content)
setattr(pc, f"image_{side}", key)
db.commit()
return _redirect(f"/postcards/{postcard_id}")