[studio] Complete editor uploads and queued cloud builds (#135)

* [studio] Complete editor uploads and queued cloud builds

* [studio] Protect unpublished source and remove unused notification RPC
This commit is contained in:
dukedotdev
2026-10-19 11:04:58 -04:00
committed by GitHub
parent 81c372135c
commit a3291bc9ce
30 changed files with 2553 additions and 142 deletions
+18 -4
View File
@@ -353,6 +353,22 @@ const app = new Hono<App>()
(c) => serveAsset(c, `avatar/${c.req.param('asset')}`)
)
// Editable Unity source manifests. Baked bundles are served separately.
.get(
'/roomunityasset/:filename{.+}',
describeRoute({
tags: ['Assets'],
summary: 'Download a Studio authoring manifest',
parameters: [keyParam('filename', 'Uploaded JSON source manifest', true)],
responses: assetResponses('The source manifest'),
}),
async (c) => {
const name = c.req.param('filename')
if (name.includes('..')) return c.body(null, 400)
return serveAsset(c, `roomunityasset/${name}`)
}
)
// Rec Room Studio room bundles. The filename is the one a room save or
// `GET …/unityasset` advertises — the R2 key itself (`studio/<roomId>/…`), as
// `unity_asset.filename` records it. Only a recorded filename is served, so this
@@ -426,16 +442,14 @@ async function serveUnityAsset(c: Context<App>) {
const name = c.req.param('filename') ?? ''
if (name.includes('..')) return c.body(null, 400)
const key = await unityAssetR2Key(c.env.DB, name)
if (!key) return c.notFound()
return serveAsset(c, key)
return serveAsset(c, key ?? `unityasset/${name}`)
}
/** Build the Studio bundle HEAD response outside Hono's HEAD-to-GET fallback. */
async function studioUnityAssetHead(env: StudioAssetEnv, name: string): Promise<Response> {
if (name.includes('..')) return new Response(null, { status: 400 })
const key = await unityAssetR2Key(env.DB, name)
if (!key) return new Response(null, { status: 404 })
const head = await env.CDN_ASSETS.head(key)
const head = await env.CDN_ASSETS.head(key ?? `unityasset/${name}`)
if (!head) return new Response(null, { status: 404 })
const headers = new Headers()
+25
View File
@@ -362,6 +362,7 @@ describe('cdn endpoints', () => {
'GET /data/{id}',
'GET /invention/{dataBlob}',
'GET /room/{dataBlob}',
'GET /roomunityasset/{filename}',
'GET /sigs/{sigName}',
'GET /unityasset/{filename}',
'HEAD /unityasset/{filename}',
@@ -377,3 +378,27 @@ describe('cdn endpoints', () => {
expect(JSON.stringify(spec).includes('"$ref"')).toBe(false)
})
})
test('Studio authoring source manifests and source files remain distinct from baked bundles', async () => {
await env.CDN_ASSETS.put(
'roomunityasset/studio-source-fixture.json',
'{"Version":3,"Files":[]}',
{ httpMetadata: { contentType: 'application/json' } }
)
const source = await exports.default.fetch(`${ORIGIN}/roomunityasset/studio-source-fixture.json`)
expect(source.status).toBe(200)
expect(await source.json()).toEqual({ Version: 3, Files: [] })
await env.CDN_ASSETS.put('unityasset/studio-source-fixture.txt', 'source data')
const file = await exports.default.fetch(`${ORIGIN}/unityasset/studio-source-fixture.txt`)
expect(await file.text()).toBe('source data')
expect(
(
await exports.default.fetch(`${ORIGIN}/unityasset/studio-source-fixture.txt`, {
method: 'HEAD',
})
).status
).toBe(200)
expect((await exports.default.fetch(`${ORIGIN}/roomunityasset/missing-source.json`)).status).toBe(
404
)
})