-
Notifications
You must be signed in to change notification settings - Fork 0
feat(seo): enhance branded og:images with 2x3 grid and unified tagline #3173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1773fad
8ebf85c
ceae15b
995cde7
41d1855
db81718
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,123 @@ | ||||||
| """OG Image endpoints for branded social media preview images.""" | ||||||
|
|
||||||
| import asyncio | ||||||
|
|
||||||
| import httpx | ||||||
| from fastapi import APIRouter, Depends, HTTPException | ||||||
| from fastapi.responses import Response | ||||||
| from sqlalchemy.ext.asyncio import AsyncSession | ||||||
|
|
||||||
| from api.cache import cache_key, get_cache, set_cache | ||||||
| from api.dependencies import optional_db | ||||||
| from core.database import SpecRepository | ||||||
| from core.images import create_branded_og_image, create_og_collage | ||||||
|
|
||||||
|
|
||||||
| router = APIRouter(prefix="/og", tags=["og-images"]) | ||||||
|
|
||||||
| # Cache TTL for generated images (1 hour) | ||||||
| OG_IMAGE_CACHE_TTL = 3600 | ||||||
|
Comment on lines
+18
to
+19
|
||||||
|
|
||||||
|
|
||||||
| async def _fetch_image(url: str) -> bytes: | ||||||
| """Fetch an image from a URL.""" | ||||||
| async with httpx.AsyncClient(timeout=30.0) as client: | ||||||
| response = await client.get(url) | ||||||
| response.raise_for_status() | ||||||
| return response.content | ||||||
|
|
||||||
|
|
||||||
| @router.get("/{spec_id}/{library}.png") | ||||||
| async def get_branded_impl_image( | ||||||
| spec_id: str, library: str, db: AsyncSession | None = Depends(optional_db) | ||||||
| ) -> Response: | ||||||
| """Get a branded OG image for an implementation. | ||||||
|
|
||||||
| Returns a 1200x630 PNG with pyplots.ai header and the plot image. | ||||||
| """ | ||||||
| # Check cache first | ||||||
| key = cache_key("og", spec_id, library) | ||||||
| cached = get_cache(key) | ||||||
| if cached: | ||||||
| return Response(content=cached, media_type="image/png", headers={"Cache-Control": "public, max-age=3600"}) | ||||||
|
|
||||||
| if db is None: | ||||||
| raise HTTPException(status_code=503, detail="Database not available") | ||||||
|
|
||||||
| repo = SpecRepository(db) | ||||||
| spec = await repo.get_by_id(spec_id) | ||||||
| if not spec: | ||||||
| raise HTTPException(status_code=404, detail="Spec not found") | ||||||
|
|
||||||
| # Find the implementation | ||||||
| impl = next((i for i in spec.impls if i.library_id == library), None) | ||||||
| if not impl or not impl.preview_url: | ||||||
| raise HTTPException(status_code=404, detail="Implementation not found") | ||||||
|
|
||||||
| try: | ||||||
| # Fetch the original plot image | ||||||
| image_bytes = await _fetch_image(impl.preview_url) | ||||||
|
|
||||||
| # Create branded image | ||||||
| branded_bytes = create_branded_og_image(image_bytes, spec_id=spec_id, library=library) | ||||||
|
|
||||||
| # Cache the result | ||||||
| set_cache(key, branded_bytes, ttl=OG_IMAGE_CACHE_TTL) | ||||||
|
||||||
| set_cache(key, branded_bytes, ttl=OG_IMAGE_CACHE_TTL) | |
| set_cache(key, branded_bytes) |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The set_cache function does not accept a ttl parameter. According to api/cache.py, set_cache only accepts (key: str, value: Any). The TTL is configured globally in the cache settings. Remove the ttl parameter from this call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding HTTP Cache-Control headers for the /og/* endpoints in the cache middleware (around line 121 in the full file). Since the og_images endpoints return static images that are already cached internally for 1 hour, adding public cache headers would allow browsers and CDNs to cache them as well, reducing server load. For example:
elif path.startswith("/og/"): response.headers["Cache-Control"] = "public, max-age=3600"