mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-25 23:18:51 -07:00
[rooms] retain beta/limitsv2 tags after clone
This commit is contained in:
@@ -1750,8 +1750,9 @@ const app = new Hono<App>()
|
|||||||
'Copies a room’s content (scene, subrooms, settings) into a new room owned by the',
|
'Copies a room’s content (scene, subrooms, settings) into a new room owned by the',
|
||||||
'caller. Cloning is the only way to make a room, so the per-account room cap is',
|
'caller. Cloning is the only way to make a room, so the per-account room cap is',
|
||||||
'enforced here — it counts the rooms the account created, minus their auto-provisioned',
|
'enforced here — it counts the rooms the account created, minus their auto-provisioned',
|
||||||
'dorm (`MAX_ROOMS_PER_ACCOUNT`; 0 lifts the cap). The clone starts with no tags,',
|
'dorm (`MAX_ROOMS_PER_ACCOUNT`; 0 lifts the cap). The clone keeps only the source’s',
|
||||||
'zeroed `Stats`, `IsRRO`/`IsDeveloperOwned`/`CloningAllowed` cleared, and PRIVATE accessibility — a new room is unpublished until its',
|
'system tags (`Type` 1: `beta`, `limitsv2` — what the scene needs to run) and drops',
|
||||||
|
'every other tag, and starts with zeroed `Stats`, `IsRRO`/`IsDeveloperOwned`/`CloningAllowed` cleared, and PRIVATE accessibility — a new room is unpublished until its',
|
||||||
'owner sets its accessibility, so it never lands in the public feeds on creation.',
|
'owner sets its accessibility, so it never lands in the public feeds on creation.',
|
||||||
'',
|
'',
|
||||||
'Rejections — a blank or taken name, the cap, a source that disallows cloning — are',
|
'Rejections — a blank or taken name, the cap, a source that disallows cloning — are',
|
||||||
|
|||||||
@@ -1334,6 +1334,14 @@ describe('rooms endpoints', () => {
|
|||||||
`UPDATE room SET data = json_set(data, '$.IsDeveloperOwned', json('true'),
|
`UPDATE room SET data = json_set(data, '$.IsDeveloperOwned', json('true'),
|
||||||
'$.CloningAllowed', json('true'), '$.Stats.VisitorCount', 42) WHERE room_id = 24`
|
'$.CloningAllowed', json('true'), '$.Stats.VisitorCount', 42) WHERE room_id = 24`
|
||||||
).run()
|
).run()
|
||||||
|
// And every kind of tag: an owner tag flagged as the genre (Type 0), the server's
|
||||||
|
// `rro` (Type 2), and the two system tags the client derives (Type 1).
|
||||||
|
await env.DB.batch([
|
||||||
|
env.DB.prepare(
|
||||||
|
`INSERT OR REPLACE INTO room_tag (room_id, tag, type, is_primary_genre)
|
||||||
|
VALUES (24, 'hangout', 0, 1), (24, 'rro', 2, 0), (24, 'beta', 1, 0), (24, 'limitsv2', 1, 0)`
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
// Clone MakerRoom (base, RoomId 24) → a fresh room owned by the caller (801).
|
// Clone MakerRoom (base, RoomId 24) → a fresh room owned by the caller (801).
|
||||||
const ok = await post(24, 'MyMakerClone')
|
const ok = await post(24, 'MyMakerClone')
|
||||||
@@ -1343,9 +1351,12 @@ describe('rooms endpoints', () => {
|
|||||||
expect(ok.value!.Name).toBe('MyMakerClone')
|
expect(ok.value!.Name).toBe('MyMakerClone')
|
||||||
expect(ok.value!.CreatorAccountId).toBe(801)
|
expect(ok.value!.CreatorAccountId).toBe(801)
|
||||||
expect(ok.value!.RoomId).toBeGreaterThan(51)
|
expect(ok.value!.RoomId).toBeGreaterThan(51)
|
||||||
// The clone starts fresh with no tags — none of the source's tags (including
|
// Only the system tags (Type 1) carry over — the scene needs them to run. The
|
||||||
// the `base` template tag) carry over.
|
// owner's tags, the `base` template tag, the derived `rro` and the genre flag don't.
|
||||||
expect(ok.value!.Tags).toEqual([])
|
expect(ok.value!.Tags).toEqual([
|
||||||
|
{ Tag: 'beta', Type: 1 },
|
||||||
|
{ Tag: 'limitsv2', Type: 1 },
|
||||||
|
])
|
||||||
// IsRRO is cleared so the client doesn't render a virtual "RRO" tag on the clone.
|
// IsRRO is cleared so the client doesn't render a virtual "RRO" tag on the clone.
|
||||||
expect(ok.value!.IsRRO).toBe(false)
|
expect(ok.value!.IsRRO).toBe(false)
|
||||||
// Nor is it developer-owned, and the new owner opts it into cloning themselves.
|
// Nor is it developer-owned, and the new owner opts it into cloning themselves.
|
||||||
@@ -1375,7 +1386,11 @@ describe('rooms endpoints', () => {
|
|||||||
Stats: Record<string, number>
|
Stats: Record<string, number>
|
||||||
}
|
}
|
||||||
expect(fetched.Name).toBe('MyMakerClone')
|
expect(fetched.Name).toBe('MyMakerClone')
|
||||||
expect(fetched.Tags).toEqual([])
|
// The system tags were written to `room_tag`, so they survive a re-read.
|
||||||
|
expect(fetched.Tags).toEqual([
|
||||||
|
{ Tag: 'beta', Type: 1 },
|
||||||
|
{ Tag: 'limitsv2', Type: 1 },
|
||||||
|
])
|
||||||
expect(fetched.IsDeveloperOwned).toBe(false)
|
expect(fetched.IsDeveloperOwned).toBe(false)
|
||||||
expect(fetched.CloningAllowed).toBe(false)
|
expect(fetched.CloningAllowed).toBe(false)
|
||||||
expect(fetched.Stats.VisitorCount).toBe(0)
|
expect(fetched.Stats.VisitorCount).toBe(0)
|
||||||
|
|||||||
@@ -658,12 +658,14 @@ export async function deleteRoomLeaderboard(
|
|||||||
/**
|
/**
|
||||||
* Clone an existing room into a new one owned by `accountId`. Copies the source
|
* Clone an existing room into a new one owned by `accountId`. Copies the source
|
||||||
* room's content (scene/subrooms/settings), assigning a fresh RoomId, the given
|
* room's content (scene/subrooms/settings), assigning a fresh RoomId, the given
|
||||||
* name, and the new owner. The clone starts with an empty tag set — the source's
|
* name, and the new owner. The clone keeps only the source's SYSTEM tags (`Type` 1,
|
||||||
* tags (including the `base` template tag) do not carry over, so the owner tags the
|
* `RoomTagType.auto` — `beta`, `limitsv2`): the client posts those to describe what the
|
||||||
* clone from scratch — `IsRRO` is cleared so the client doesn't render a virtual
|
* scene needs to run, so a copy of the scene needs them too. Every other tag (the owner's
|
||||||
* "RRO" tag on it, `IsDeveloperOwned`/`CloningAllowed` are cleared, its `Stats` start
|
* `Type` 0 tags, the `base` template tag, the server-derived `rro`) is dropped, so the
|
||||||
* at zero, and it starts PRIVATE rather than inheriting the source's visibility. Returns the new room, or null when the source isn't in D1 or disallows
|
* owner tags the clone from scratch. `IsRRO` is cleared so the client doesn't render a
|
||||||
* cloning.
|
* virtual "RRO" tag on it, `IsDeveloperOwned`/`CloningAllowed` are cleared, its `Stats`
|
||||||
|
* start at zero, and it starts PRIVATE rather than inheriting the source's visibility.
|
||||||
|
* Returns the new room, or null when the source isn't in D1 or disallows cloning.
|
||||||
*/
|
*/
|
||||||
export async function cloneRoom(
|
export async function cloneRoom(
|
||||||
db: D1Database,
|
db: D1Database,
|
||||||
@@ -691,14 +693,23 @@ export async function cloneRoom(
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// The system tags (`Type` 1: `beta`, `limitsv2`) carry over — the client derives them
|
||||||
|
// from what the scene is built with and the clone is the same scene, so without them
|
||||||
|
// the copy doesn't work. Nothing else does: no owner tags, no `base`, no `rro`, and no
|
||||||
|
// primary-genre flag (an auto tag is never the genre).
|
||||||
|
const sourceTags = Array.isArray(source.Tags) ? (source.Tags as RoomTag[]) : []
|
||||||
|
const systemTags: RoomTag[] = sourceTags
|
||||||
|
.filter((t) => t.Type === RoomTagType.auto)
|
||||||
|
.map((t) => ({ Tag: t.Tag, Type: RoomTagType.auto }))
|
||||||
|
|
||||||
const cloned: Room = {
|
const cloned: Room = {
|
||||||
...source,
|
...source,
|
||||||
RoomId: newRoomId,
|
RoomId: newRoomId,
|
||||||
Name: name,
|
Name: name,
|
||||||
CreatorAccountId: accountId,
|
CreatorAccountId: accountId,
|
||||||
IsDorm: false,
|
IsDorm: false,
|
||||||
// Start fresh: drop every tag the source carried (including `base`).
|
// Start fresh, except for the system tags the scene needs to run (see below).
|
||||||
Tags: [],
|
Tags: systemTags,
|
||||||
// A user clone is not a Rec Room Original — clear the inherited flag, or the
|
// A user clone is not a Rec Room Original — clear the inherited flag, or the
|
||||||
// client renders a virtual "RRO" tag on the clone.
|
// client renders a virtual "RRO" tag on the clone.
|
||||||
IsRRO: false,
|
IsRRO: false,
|
||||||
@@ -722,6 +733,8 @@ export async function cloneRoom(
|
|||||||
// serializeRoom drops the hydrated SubRooms from the blob; the clone's subrooms are
|
// serializeRoom drops the hydrated SubRooms from the blob; the clone's subrooms are
|
||||||
// inserted into the subroom table below with fresh globally-unique ids.
|
// inserted into the subroom table below with fresh globally-unique ids.
|
||||||
await db.prepare('INSERT INTO room (data) VALUES (?1)').bind(serializeRoom(cloned)).run()
|
await db.prepare('INSERT INTO room (data) VALUES (?1)').bind(serializeRoom(cloned)).run()
|
||||||
|
// Tags live in `room_tag`, not the blob: the system tags need their own write.
|
||||||
|
if (systemTags.length > 0) await setRoomTags(db, newRoomId, systemTags)
|
||||||
const sourceSubRooms = Array.isArray(source.SubRooms) ? (source.SubRooms as SubRoom[]) : []
|
const sourceSubRooms = Array.isArray(source.SubRooms) ? (source.SubRooms as SubRoom[]) : []
|
||||||
const clonedSubRooms: SubRoom[] = []
|
const clonedSubRooms: SubRoom[] = []
|
||||||
for (const sub of sourceSubRooms) {
|
for (const sub of sourceSubRooms) {
|
||||||
|
|||||||
Reference in New Issue
Block a user