media: runtime thumbnail service for attachments (currently serves originals as-is)
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 52/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Quiet
- Tech stack
- typescript
- Domain
- api, backend, performance
Research direction
Start at the GET /api/attachments/:key endpoint and compare the sharp-based resize pattern in apps/api/src/routes/people.ts. Implement read-time variants using the original blob and the requested w, h, fit, and format parameters, with caching by blobHash and those parameters. Done means repeated requests reuse the cached resized output and originals remain stored unchanged.
Written by the indexing model from the issue text.
Description
What's deferred
Blog-post bodies (after PR #107 + follow-up) store original media bytes as gitsheets attachments under `blog-posts//.`. The API serves them via `GET /api/attachments/:key` — but as the original bytes, no resizing.
Practical consequences:
- A blog index card that wants a 200×200 thumbnail downloads the full 2MB JPEG and lets CSS scale it.
- A featured-image header that wants a 1920px wide hero downloads the same 2MB JPEG regardless of viewport.
- Per-render bytes-on-the-wire are 5-50× larger than they need to be.
We accepted this because:
- The legacy laddr server's `/thumbnail//` endpoint can't be relied on past cutover.
- Storing originals in the data repo is the right durable record.
- Resizing on demand is a runtime concern that can be added without changing the data shape.
Fix shape
Add a thumbnail endpoint:
```
GET /api/attachments/:key?w=200&h=200&fit=cover&format=auto
```
- Reads the original blob from gitsheets.
- Pipes through `sharp` (already a dep — used in avatar uploads).
- Caches the resized output keyed by `(blobHash, w, h, fit, format)` so the same params return from cache after the first hit.
- Cache lives in the pod's memory or a tmpfs volume — invalidated on hot-reload (the blobHash key handles that naturally).
Alternative: precompute thumbnails at import time (write `-200x200.jpg` alongside `.jpg`). Trades repo size for runtime cost. Probably the wrong trade — variants explode quickly.
Until then
Markdown bodies render `` tags pointing at `/api/attachments/...` and the browser pulls the full original. SPA can add `loading="lazy"` and CSS `max-width` for ergonomics; bandwidth cost is real but not blocking v1.
Related
- Avatar upload (`apps/api/src/routes/people.ts`) already does a sharp-based resize at write time + stores both originals and 128×128 thumbnails. The pattern is established; the read-time variant would unify the two surfaces.
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 1
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 25
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from CodeForPhilly/codeforphilly-ng
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
MarkdownEditor toolbar: use Radix Toolbar from radix-ui instead of the hand-rolled roving tabindex Openenhancement
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
All issues in CodeForPhilly/codeforphilly-ng
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
ontola/atomic-server#1625 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
melgarafael/DeskcommCRM#1451 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 82/100
-
bug via-triage
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bot:ai-assisted component:compact-js status:untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
midnightntwrk/midnight-sdk#403 ·