more endpoints, fix orientation on new accounts

This commit is contained in:
Devin Zuczek
2026-06-15 22:39:24 -04:00
parent 5f3ec3b8c7
commit 335b4d68ce
16 changed files with 554 additions and 87 deletions
+11 -4
View File
@@ -431,16 +431,23 @@ const app = new Hono<App>({ strict: false })
.get('/api/images/v2/named', (c) => c.json([])) // TODO: hydrate from JSON/namedimages.json
.post('/api/images/v4/uploadsaved', async (c) => {
const body = await c.req.parseBody().catch(() => ({}) as Record<string, unknown>)
const file = body.file
if (!(file instanceof File)) return c.json({ error: 'No file found in request' }, 400)
// The client posts the file as `image`; accept `file` too for safety.
const candidate = body.image ?? body.file
if (!(candidate instanceof File)) return c.json({ error: 'No file found in request' }, 400)
const file = candidate
const valid = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp']
const dot = file.name.lastIndexOf('.')
const ext = dot >= 0 ? file.name.slice(dot).toLowerCase() : ''
const extension = valid.includes(ext) ? ext : '.png'
// TODO: persist the upload (R2?). For now just mint a name.
return c.json({ ImageName: crypto.randomUUID().replace(/-/g, '') + extension })
// Store the upload in the shared image bucket under a random key. The `img`
// worker serves it back by that key, which is the returned ImageName.
const name = crypto.randomUUID().replace(/-/g, '') + extension
await c.env.IMAGES.put(name, await file.arrayBuffer(), {
httpMetadata: { contentType: file.type || 'image/png' },
})
return c.json({ ImageName: name })
})
// ---- Rooms ----------------------------------------------------------------
+3
View File
@@ -5,6 +5,9 @@ export type Env = SharedHonoEnv & {
// Shared rooms database (schema/migrations owned by the `rooms` worker). Used
// read-only here for the /roomserver/rooms/* endpoints.
DB: D1Database
// Image bucket (shared with the `img` worker, which serves objects back by
// key). Uploaded saved images are written here.
IMAGES: R2Bucket
}
/** Variables can be extended */
+30
View File
@@ -337,3 +337,33 @@ describe('room server', () => {
expect(await res.json()).toEqual({ Cheered: false, Favorited: false })
})
})
describe('images', () => {
test('POST /api/images/v4/uploadsaved stores the file in R2 and returns its name', async () => {
const bytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 1, 2, 3, 4])
const fd = new FormData()
fd.append('image', new File([bytes], 'avatar.png', { type: 'image/png' }))
const res = await exports.default.fetch(`${ORIGIN}/api/images/v4/uploadsaved`, {
method: 'POST',
body: fd,
})
expect(res.status).toBe(200)
const { ImageName } = (await res.json()) as { ImageName: string }
expect(ImageName).toMatch(/^[0-9a-f]+\.png$/)
// The object is in the shared bucket under that key.
const stored = await env.IMAGES.get(ImageName)
expect(stored).not.toBeNull()
expect(new Uint8Array(await stored!.arrayBuffer())).toEqual(bytes)
})
test('POST /api/images/v4/uploadsaved 400s without a file', async () => {
const res = await exports.default.fetch(`${ORIGIN}/api/images/v4/uploadsaved`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'foo=bar',
})
expect(res.status).toBe(400)
})
})
+8
View File
@@ -18,6 +18,14 @@
"database_id": "d44083e1-5bfe-4467-aa9a-f13c5c2496d5"
}
],
// Image bucket shared with the `img` worker (which serves objects back by key).
// Saved-image uploads are written here.
"r2_buckets": [
{
"binding": "IMAGES",
"bucket_name": "rec-img"
}
],
"logpush": false,
"upload_source_maps": true,
"observability": {