[studio] avatar authoring, managed prefab builds (#140)

This commit is contained in:
dukedotdev
2026-10-23 15:25:31 -04:00
committed by GitHub
parent 16e5f61160
commit 3da35a8692
22 changed files with 2059 additions and 81 deletions
+3
View File
@@ -56,6 +56,8 @@ const UPLOAD_SUBFOLDER: Record<number, string> = {
8: 'room',
9: 'roomunityasset',
10: 'unityasset',
13: 'avatar',
14: 'avatarunityasset',
}
/** Resolve the storage subfolder for a posted FileType, or `undefined` when unknown. */
@@ -380,6 +382,7 @@ const app = new Hono<App>()
contentType = image.contentType
}
await c.env.CDN_ASSETS.put(`${subfolder}/${filename}`, bytes, {
customMetadata: { ownerId: String(id) },
httpMetadata: { contentType },
// Record the SHA-256 on the object. R2 stores an md5 on its own, but the
// hashes the client is served (an invention's `BlobHash`) are SHA-256, and
@@ -21,6 +21,27 @@ beforeAll(async () => {
// storage worker's validation accepts it.
const TEST_SECRET = 'test-signing-key'
it('stores avatar source and baked uploads with immutable account ownership metadata', async () => {
for (const [type, prefix] of [
[13, 'avatar'],
[14, 'avatarunityasset'],
] as const) {
const form = new FormData()
form.set('fileType', String(type))
form.set('file', new File(['avatar source'], 'source.bin'))
const r = await SELF.fetch(ORIGIN + '/upload', {
method: 'POST',
headers: { ...(await bearer()), 'User-Agent': 'RRStudio' },
body: form,
})
expect(r.status).toBe(200)
const body = await r.json<{ Filename: string; Hash: string }>()
expect(body.Hash).toBeTruthy()
const object = await env.CDN_ASSETS.head(prefix + '/' + body.Filename)
expect(object?.customMetadata?.ownerId).toBe('42')
}
})
function b64url(input: ArrayBuffer | string): string {
const bytes = typeof input === 'string' ? new TextEncoder().encode(input) : new Uint8Array(input)
let binary = ''