mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-25 23:18:51 -07:00
[rooms] fix cloud variables
This commit is contained in:
@@ -255,7 +255,10 @@ export const SubRoomDto = z.object({
|
||||
'The latest room save — where the client finds the scene blob. Null until first save'
|
||||
),
|
||||
DataBlob: z.string().optional().describe('Legacy flat key; the client reads `CurrentSave`'),
|
||||
RoomDataBlob: z.string().optional().describe('Uploaded room-data key; absent until first save'),
|
||||
RoomDataBlob: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Legacy; the room-level blob is the ROOM’s `DataBlob`. Not a key the client knows'),
|
||||
DataSavedAt: z.string().optional().describe('ISO timestamp of the last save'),
|
||||
PersistenceVersion: z.int().optional(),
|
||||
InventionUsage: z.string().optional().describe('Recorded by a room save; absent until then'),
|
||||
@@ -304,6 +307,11 @@ export const RoomDto = z.object({
|
||||
PublishedAt: z.string(),
|
||||
BecameRRStudioRoomAt: z.string().nullable(),
|
||||
Stats: RoomStatsDto,
|
||||
DataBlob: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('The room-level metadata blob a room save uploads as `RoomData`; absent until one'),
|
||||
DataBlobHash: z.string().nullable().optional().describe('`RoomData.Hash` from that save'),
|
||||
BoostCount: z
|
||||
.int()
|
||||
.describe('Boosts on the room. Nothing grants boosts here, so always 0 — but present'),
|
||||
@@ -877,9 +885,11 @@ export const SaveSubRoomDataRequest = z.object({
|
||||
.optional()
|
||||
.describe('The uploaded scene-data blob — becomes the subroom’s `CurrentSave.DataBlob`'),
|
||||
RoomData: z
|
||||
.object({ Filename: z.string() })
|
||||
.object({ Filename: z.string(), Hash: z.string().nullable().optional() })
|
||||
.optional()
|
||||
.describe('The uploaded room-level data blob — becomes `RoomDataBlob`'),
|
||||
.describe(
|
||||
'The uploaded room-level metadata blob — becomes the ROOM’s `DataBlob`/`DataBlobHash`'
|
||||
),
|
||||
Description: z
|
||||
.string()
|
||||
.optional()
|
||||
|
||||
@@ -3606,10 +3606,11 @@ const app = new Hono<App>()
|
||||
if (!canManageRoom(room, accountId)) return c.body(null, 403)
|
||||
|
||||
// The client uploads BOTH blobs to `storage` first and sends their keys here:
|
||||
// `SubRoomData` is the scene blob (what the loader downloads), `RoomData` the
|
||||
// metadata blob. `OwnershipProof` is accepted and ignored.
|
||||
// `SubRoomData` is the scene blob (what the loader downloads, onto the subroom's
|
||||
// save), `RoomData` the room-level metadata blob (onto the ROOM, as `DataBlob`).
|
||||
// `OwnershipProof` is accepted and ignored.
|
||||
const body = (await c.req.json().catch(() => ({}))) as {
|
||||
RoomData?: { Filename?: string }
|
||||
RoomData?: { Filename?: string; Hash?: string | null }
|
||||
SubRoomData?: { Filename?: string; Hash?: string | null }
|
||||
UnityAssetId?: string | null
|
||||
Description?: string
|
||||
@@ -3623,6 +3624,7 @@ const app = new Hono<App>()
|
||||
subRoomDataHash:
|
||||
typeof body.SubRoomData?.Hash === 'string' ? body.SubRoomData.Hash : undefined,
|
||||
roomDataFilename: body.RoomData?.Filename,
|
||||
roomDataHash: typeof body.RoomData?.Hash === 'string' ? body.RoomData.Hash : undefined,
|
||||
unityAssetId: typeof body.UnityAssetId === 'string' ? body.UnityAssetId : undefined,
|
||||
autoPublish: body.AutoPublish === true,
|
||||
description: typeof body.Description === 'string' ? body.Description : undefined,
|
||||
|
||||
@@ -3552,10 +3552,20 @@ describe('rooms endpoints', () => {
|
||||
(s) => s.SubRoomId === 2
|
||||
)!
|
||||
expect(savedSub).toMatchObject({
|
||||
RoomDataBlob: '5c618c920f6247efb8327e327d0b4417',
|
||||
CreatorAccountId: 1,
|
||||
PersistenceVersion: 41,
|
||||
})
|
||||
// `RoomData` is the ROOM-level metadata blob: it lands on the room as `DataBlob`, which
|
||||
// is where the client reads it — not on the subroom, which has no field for it.
|
||||
expect(savedSub).not.toHaveProperty('RoomDataBlob')
|
||||
expect(saved.value.room).toMatchObject({
|
||||
DataBlob: '5c618c920f6247efb8327e327d0b4417',
|
||||
DataBlobHash: null,
|
||||
})
|
||||
expect(await (await SELF.fetch(`${ORIGIN}/rooms/2`)).json()).toMatchObject({
|
||||
DataBlob: '5c618c920f6247efb8327e327d0b4417',
|
||||
DataBlobHash: null,
|
||||
})
|
||||
expect(savedSub.CurrentSave).toMatchObject({
|
||||
DataBlob: 'a84167b16796452ab70ee8a6a5b1dc5f',
|
||||
})
|
||||
|
||||
@@ -37,7 +37,12 @@ import type { App } from './context'
|
||||
*
|
||||
* RoomSave (1) lands under `room/` (not `roomsave/`) so the `cdn` worker's
|
||||
* `GET /room/:dataBlob` route serves the blob back — both bind the same
|
||||
* `recflare-cdn` bucket, so the key prefixes must match.
|
||||
* `recflare-cdn` bucket, so the key prefixes must match. RoomMetadata (6) — the
|
||||
* room-level blob a room save posts as `RoomData`, which the room then names as its
|
||||
* `DataBlob` — lands under `room/` too: the client fetches it from the same
|
||||
* `/room/<name>` path it fetches the scene from. It once went under `roommetadata/`,
|
||||
* where nothing ever asked for it. The two never collide — every upload is named by
|
||||
* a fresh GUID under the day's folder.
|
||||
*/
|
||||
const UPLOAD_SUBFOLDER: Record<number, string> = {
|
||||
1: 'room',
|
||||
@@ -45,7 +50,7 @@ const UPLOAD_SUBFOLDER: Record<number, string> = {
|
||||
3: 'image',
|
||||
4: 'video',
|
||||
5: 'invention',
|
||||
6: 'roommetadata',
|
||||
6: 'room',
|
||||
}
|
||||
|
||||
/** Resolve the storage subfolder for a posted FileType, or `undefined` when unknown. */
|
||||
|
||||
@@ -63,7 +63,7 @@ it('POST /upload 401s without a token', async () => {
|
||||
expect(res.status).toBe(401)
|
||||
})
|
||||
|
||||
it('POST /upload stores a RoomMetadata (FileType 6) file under roommetadata/ and returns its name', async () => {
|
||||
it('POST /upload stores a RoomMetadata (FileType 6) file under room/ and returns its name', async () => {
|
||||
// Mirrors the client's multipart upload: FileType=6, File=<binary>.
|
||||
const bytes = new Uint8Array([0x10, 0x02, 0x1a, 0x00])
|
||||
const res = await SELF.fetch(`${ORIGIN}/upload`, {
|
||||
@@ -77,8 +77,9 @@ it('POST /upload stores a RoomMetadata (FileType 6) file under roommetadata/ and
|
||||
/^\d{4}-\d{2}-\d{2}\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
||||
)
|
||||
|
||||
// The bytes are persisted in the shared CDN bucket under the type subfolder.
|
||||
const stored = await env.CDN_ASSETS.get(`roommetadata/${filename}`)
|
||||
// The bytes are persisted in the shared CDN bucket under `room/`, the same folder as a
|
||||
// scene blob: the client downloads the room's `DataBlob` from the cdn's `/room/<name>`.
|
||||
const stored = await env.CDN_ASSETS.get(`room/${filename}`)
|
||||
expect(stored).not.toBeNull()
|
||||
expect(new Uint8Array(await stored!.arrayBuffer())).toEqual(bytes)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user