mirror of
https://github.com/saltbo/zpan.git
synced 2026-08-28 15:51:29 +08:00
3064403c92
Keep historical identifiers readable and addressable while generating all new entity IDs with Base62. Reject unsafe external ID references and preserve API keys and device codes during optional normalization.
26 lines
1.2 KiB
TypeScript
26 lines
1.2 KiB
TypeScript
import type { Context } from 'hono'
|
|
import { OPAQUE_ID_PATTERN } from '../../shared/ids'
|
|
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 scope = c.req.param('scope')
|
|
const id = c.req.param('id')
|
|
if ((scope !== 'user' && scope !== 'team') || !id || !OPAQUE_ID_PATTERN.test(id)) return c.body(null, 404)
|
|
|
|
const bucket = c.get('platform').getBinding<R2BucketLike>(PUBLIC_IMAGES_BINDING)
|
|
if (!bucket) return c.body(null, 404)
|
|
|
|
const obj = await bucket.get(`${scope}/${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',
|
|
})
|
|
}
|