yingjieli-image-store v1.0.0
subsystem yingjieli.site
capsule://quake0day/[email protected]
Stores and serves artwork images for yingjieliartist.com. Uploads land
in Cloudflare R2 (binding YL_IMAGES); reads go through a Workers Cache
layer with long-immutable cache headers and ETag support.
Owns
- POST /api/upload (multipart; admin-only)
- GET /api/img/<key> (public, cached)
- DELETE /api/img/<key> (admin-only)
- the R2 key format: <slug>_<timestamp36><rand>.{jpg|png|webp}
- the 8 MB max upload limit and the JPEG/PNG/WebP allow-list
- filename sanitization (lowercase, [a-z0-9_], max 60 chars)
Does not own
- image resizing (the client pre-resizes before upload)
- mapping images to artwork records (content-store does that)
- who is allowed to upload/delete (delegates to yingjieli-admin-auth)
AI orientation
Images live in R2; their primary URL is /api/img/<key>. Keys are
server-generated from sanitized base name + timestamp + 4 random chars
so the client cannot dictate the final key. The Workers Cache API is
a hot layer in front of R2 — never cache responses behind a session.
Avoid
- Trusting client-supplied keys; the server always generates them.
- Returning R2 objects through paths other than /api/img/<key>.
- Allowing path traversal in keys (/, .. are rejected with 400).
Provides
http_api:image-uploadhttp_api:image-servehttp_api:image-delete
Requires
library:auth-helpersfromyingjieli-admin-authenv:YL_IMAGES— Cloudflare R2 bucket binding.
Dependencies
Capsules
yingjieli-admin-auth>=1.0.0 <2.0.0
Runtime
node>=18cloudflare-pages*
Invariants (must always hold)
- The server never serves an image whose R2 key was supplied verbatim by the client.
- Path-traversal patterns (`/`, `..`) are rejected with 400, never with 200.
- Anonymous DELETE is impossible — auth is checked before R2.delete().
Source files (2)
Click any file to view its content; the path on the right shows where the file lands when this capsule is installed.
src/api/upload.js→functions/api/upload.jssrc/api/img/[name].js→functions/api/img/[name].js
Plus capsule.yaml and
install.json.