mirror of
https://github.com/saltbo/zpan.git
synced 2026-08-28 15:51:29 +08:00
f24c6e2021
* fix(avatar): refresh session after change and show fallback on remove
Two avatar-display bugs surfaced post-#456:
- After uploading/removing an avatar the UI showed the old image until a full
reload. refreshSession() now calls getSession({ disableCookieCache: true }) to
re-read user.image past the 5-min session cookie cache, then
$store.notify('$sessionSignal') so useSession() actually refetches and
re-renders (an external endpoint never toggles better-auth's session signal).
- Removing an avatar left a blank circle: the conditional `{user.image && <AvatarImage>}`
unmounts the radix Image, which keeps a stale "loaded" status so the Fallback
stays hidden. Always render <AvatarImage src={user?.image ?? undefined}> so radix
re-runs its loading status (src -> undefined => "error") and shows the initials.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(avatar): self-host avatars on R2 when deployed on Workers
The image-upload gateway now branches per request on the AVATARS R2 binding:
- binding present (Cloudflare) -> upload straight to R2 (key `scope/id`, content
type in R2 metadata, a content-hash `?v=` cache-buster) and return either an
AVATARS_PUBLIC_URL (R2 custom domain) URL or a relative /api/avatar-blobs URL.
- binding absent (Node/Docker, or a Worker without it) -> the existing ZPan Cloud
avatar service, unchanged.
Adds a public GET /api/avatar-blobs/:scope/:id route that streams the blob from
the AVATARS binding (so local miniflare, which gives R2 no public URL, can serve
avatars too). AVATARS_BINDING / R2BucketLike live in platform/interface so both
the adapter and the http route can use them without crossing the arch boundary.
wrangler.toml declares the AVATARS bucket (prod + staging).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(deploy): provision the zpan-avatars R2 bucket + AVATARS_PUBLIC_URL
The AVATARS R2 binding added for self-hosted avatars needs the bucket to exist on
deploy. Mirror the resource-provisioning pattern (D1/Queue): create zpan-avatars if
missing, enable its managed public URL, and upsert AVATARS_PUBLIC_URL so prod serves
avatars straight from R2 (zero Worker egress). Without the secret the app still works
via its /api/avatar-blobs route.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(avatar): reuse the original PUBLIC_IMAGES bucket/binding/env names
Keep the same Cloudflare resource names as before #456 removed them so the existing
`zpan-public-images` bucket is reused (not orphaned) and the API token scopes still
apply: R2 binding PUBLIC_IMAGES, bucket zpan-public-images(-staging), public-URL
secret PUBLIC_IMAGES_URL. Pure rename of the AVATARS naming I'd introduced — no
behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
21 lines
1013 B
TypeScript
21 lines
1013 B
TypeScript
import type { Context } from 'hono'
|
|
import type { Env } from '../middleware/platform'
|
|
import { PUBLIC_IMAGES_BINDING, type R2BucketLike } from '../platform/interface'
|
|
|
|
// Public read of a self-hosted avatar blob from the PUBLIC_IMAGES R2 binding. Only used on
|
|
// Cloudflare when the binding is present and PUBLIC_IMAGES_URL is NOT set (e.g. local
|
|
// miniflare, which gives R2 no public URL); with a custom domain set, or on Node/Docker
|
|
// (Cloud avatar service), the stored URL is absolute and this route is never hit.
|
|
export async function serveAvatarBlob(c: Context<Env>) {
|
|
const bucket = c.get('platform').getBinding<R2BucketLike>(PUBLIC_IMAGES_BINDING)
|
|
if (!bucket) return c.body(null, 404)
|
|
|
|
const obj = await bucket.get(`${c.req.param('scope')}/${c.req.param('id')}`)
|
|
if (!obj) return c.body(null, 404)
|
|
|
|
return c.body(await obj.arrayBuffer(), 200, {
|
|
'Content-Type': obj.httpMetadata?.contentType ?? 'application/octet-stream',
|
|
'Cache-Control': 'public, max-age=31536000, immutable',
|
|
})
|
|
}
|