fix: return placeholder SVG for missing photo files #377
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/basketball-api!377
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "374-missing-photo-placeholder"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
When a player's
photo_urlreferences a file that no longer exists on disk,ImageStaticFilesnow returns a silhouette SVG placeholder instead of a raw 404. This prevents broken-image icons in the browser.Changes
src/basketball_api/static.py— Overrideget_responseto catch 404HTTPExceptionand return a lightweight silhouette SVG placeholder withimage/svg+xmlcontent typetests/test_photo_content_type.py— Updatetest_nonexistent_file_404totest_nonexistent_file_returns_placeholder, asserting 200 status and SVG contentTest Plan
pytest tests/test_photo_content_type.py— 6/6 pass (including the new placeholder test)pytest tests/test_upload.py— 77 pass, no regressionsReview Checklist
Related Notes
9b4ba04ef0a7ae05aaffQA Review
Scope: 2 files changed, 36 additions, 3 deletions. Scoped correctly to issue #374.
static.py
get_responseto catchHTTPException(404)and return a placeholder is the minimal, correct interception point. Existing file serving is untouched._PLACEHOLDER_RESPONSE-- StarletteResponseobjects are safe to reuse across requests since they are immutable after construction. No concurrency issue.tryouts.py:195-201, consistent with the codebase pattern.test_photo_content_type.py
incheck, which handles presence/absence ofcharsetparameter correctly.Nits
Note
Initial push included an unrelated
is_publiccommit from local main. Rebased toorigin/mainso the PR now contains only the placeholder fix commit.VERDICT: APPROVED
PR #377 Review
DOMAIN REVIEW
Stack: Python / FastAPI / Starlette / pytest
Correctness
get_responseoverride correctly catchesHTTPExceptionwith status 404 and returns the placeholder. Non-404 exceptions are re-raised. This is sound.media_type="image/svg+xml"is correctly set on the placeholder response. Browsers will render it as an image./uploads/photosonly (main.py:101-102), so the fallback is scoped exclusively to photo requests. Non-photo static paths are unaffected.Performance
_PLACEHOLDER_RESPONSEis a module-level singleton -- allocated once, returned on every 404. No disk reads, no string concatenation per request. Good pattern.Responseobjects are safe to share across requests since the body is set at construction time and headers are regenerated per call by the ASGI interface.DRY: SVG duplication with tryouts.py
tryouts.py:196-201defines_SILHOUETTE_SVGwith the same<path>data but different SVG attributes (nofill, nowidth/height).static.pydefines_PLACEHOLDER_SVGwithfill="#9e9e9e" width="100%" height="100%"added to the<svg>element.M12 12c2.76...) is identical -- this is the Material Design "person" icon. The attribute differences are intentional (tryouts embeds it inline in HTML where CSS controls sizing/color; static.py serves it standalone where the SVG must carry its own styling). This is acceptable divergence, not a DRY blocker.Existing flow preserved
file_responseis untouched. Real files still get the explicit MIME type override. The newget_responsesits abovefile_responsein the call chain and only intercepts when the parent raises 404.Test coverage
test_nonexistent_file_returns_placeholderasserts: status 200,image/svg+xmlin content-type,<svgandviewBoxpresent in body. Covers the happy path for the fallback.BLOCKERS
None.
NITS
Extract shared SVG path to a constant: The
M12 12c2.76...path data is duplicated betweenstatic.pyandroutes/tryouts.py. Consider extracting the path data (not the full SVG) to a shared constant in a common module. Low priority since the two usages intentionally differ in SVG attributes, but it would prevent future divergence of the path itself.Test could assert exact content-type: The test checks
"image/svg+xml" in response.headers["content-type"](substring match). An exact match== "image/svg+xml; charset=utf-8"or at minimumresponse.headers["content-type"].startswith("image/svg+xml")would be marginally more precise, though the current approach is fine in practice.SOP COMPLIANCE
374-missing-photo-placeholdermatches issue #374Closes forgejo_admin/basketball-api#374, board item #879PROCESS OBSERVATIONS
VERDICT: APPROVED