gendesign/backend/app/api/v1/photos.py
2026-04-27 20:26:55 +03:00

87 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Serve cached DOM.РФ photos.
GET /api/v1/photos/{obj_id}/{file_id}?size=thumb|full
- size=thumb (default): WebP 320×240 from local cache. If thumb is missing
but the original is present locally, generate one on the fly. If neither
is present, redirect to the upstream DOM.РФ URL.
- size=full: original PNG/JPG if cached locally, else redirect upstream.
This isolates the frontend grid from upstream latency and from Next.js dev-mode
optimizer cold-hit cost.
"""
from __future__ import annotations
import logging
from pathlib import Path
from typing import Annotated, Literal
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import FileResponse, RedirectResponse, Response
from sqlalchemy import text
from sqlalchemy.orm import Session
from app.core.db import get_db
from app.services.photos.thumbs import make_thumbnail
logger = logging.getLogger(__name__)
router = APIRouter()
_PHOTO_LOOKUP_SQL = text(
"""
SELECT local_path, thumb_path, photo_url
FROM domrf_kn_photos
WHERE obj_id = :obj AND obj_file_id = :fid
LIMIT 1
"""
)
@router.get("/{obj_id}/{file_id}")
def get_photo(
db: Annotated[Session, Depends(get_db)],
obj_id: int,
file_id: str,
size: Annotated[Literal["thumb", "full"], Query()] = "thumb",
) -> Response:
row = db.execute(_PHOTO_LOOKUP_SQL, {"obj": obj_id, "fid": file_id}).mappings().first()
if not row:
raise HTTPException(status_code=404, detail="photo not registered")
local_path = row["local_path"]
thumb_path = row["thumb_path"]
upstream = row["photo_url"]
headers = {"Cache-Control": "public, max-age=604800, immutable"}
if size == "full":
if local_path and Path(local_path).exists():
return FileResponse(local_path, headers=headers)
if upstream:
return RedirectResponse(upstream, status_code=302)
raise HTTPException(status_code=404, detail="full photo unavailable")
# size=thumb
if thumb_path and Path(thumb_path).exists():
return FileResponse(thumb_path, media_type="image/webp", headers=headers)
# Generate thumb on the fly from cached original
if local_path and Path(local_path).exists():
generated = make_thumbnail(Path(local_path))
if generated and generated.exists():
db.execute(
text(
"UPDATE domrf_kn_photos SET thumb_path = :tp"
" WHERE obj_id = :o AND obj_file_id = :f"
),
{"tp": str(generated), "o": obj_id, "f": file_id},
)
db.commit()
return FileResponse(str(generated), media_type="image/webp", headers=headers)
# Last resort — redirect to upstream so the page still renders
if upstream:
return RedirectResponse(upstream, status_code=302)
raise HTTPException(status_code=404, detail="photo unavailable")