fix: add /posts/{slug} detail route so post titles resolve
Post cards on the home page have linked to /posts/<slug> since
Phase 1 (per the partial's inline comment), but the matching route
and template were never registered — clicking a post title returned
a JSON 404 from FastAPI. This adds:
- PostService.get_published_by_slug() — status-filtered, parameterized
read that treats "draft" and "unknown slug" as the same 404 so
unpublished titles cannot be enumerated via URL guessing.
- GET /posts/{slug} public route that 404s on miss.
- public/post.html detail template mirroring about.html's safe-render
pattern for the bleach-sanitized body_html_cached.
- Supporting .page-article__date / .page-article__back CSS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,7 @@ from fastapi.responses import HTMLResponse, Response
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from app.config import Settings, get_settings
|
||||
from app.models.entities import Page
|
||||
from app.models.entities import Page, Post
|
||||
from app.models.posts import PostSummary
|
||||
from app.services.contact import ContactService
|
||||
from app.services.hcaptcha import HCaptchaService
|
||||
@@ -117,6 +117,33 @@ def home(
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/posts/{slug}",
|
||||
response_class=HTMLResponse,
|
||||
summary="Single blog post",
|
||||
)
|
||||
def post_detail(
|
||||
slug: str,
|
||||
request: Request,
|
||||
templates: Jinja2Templates = Depends(get_templates),
|
||||
posts: PostService = Depends(get_post_service),
|
||||
) -> HTMLResponse:
|
||||
"""Render a single published post by slug.
|
||||
|
||||
Drafts and unknown slugs return 404 (same response so a mistyped
|
||||
URL cannot be used to enumerate unpublished titles).
|
||||
"""
|
||||
post: Post | None = posts.get_published_by_slug(slug)
|
||||
if post is None:
|
||||
raise HTTPException(status_code=404, detail="Post not found")
|
||||
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
"public/post.html",
|
||||
{"active_nav": "home", "post": post},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/about", response_class=HTMLResponse, summary="About the farm")
|
||||
def about(
|
||||
request: Request,
|
||||
|
||||
Reference in New Issue
Block a user