Hosted rooms: the hub's side
CI / test (push) Successful in 1m5s

Rooms made on the website (hosted_room, migration 0005), the REST routes in
docs/hosted-rooms.md, and the realtime room.* protocol: joining with the
capacity, adult and linked-account rules, seats, numbered messages with
forged senders dropped and a rate limit, closing. The Rooms page lists them,
with a form to make one and the owner's edit and close.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-30 03:21:21 -07:00
1 parent 0c67232425
commit 2a1fc94df6
18 files changed
+3138 -52

No files matched your search

+30
View File
@@ -331,3 +331,33 @@ export const library = pgTable(
},
(t) => [primaryKey({ columns: [t.userId, t.productId] })],
);
// ---- T5DE-hosted rooms (docs/hosted-rooms.md) ----
/** A scene item, as the client's room_contents: [furniture instance ID, product ID (IMVU's or the hub's), placement]. */
export type SceneItem = [number, number | string, string];
// A room hosted by the hub: its instance ID is "2100000000-<n>", its chat ID 2100000000 + n. Who's in it is kept in
// memory only (apps/api/src/realtime.ts). Closing it keeps the row, so its number is never reused.
export const hostedRoom = pgTable(
"hosted_room",
{
n: serial("n").primaryKey(),
ownerId: text("owner_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
// One of the owner's linked IMVU accounts, shown as the room's owner (the client lets it set the scene).
ownerImvuId: bigint("owner_imvu_id", { mode: "number" }).notNull(),
name: text("name").notNull(),
description: text("description").notNull().default(""), // plain text
roomPid: bigint("room_pid", { mode: "number" }).notNull(), // the room shell, an IMVU product
rating: text("rating").notNull().default("general"), // "general", "adult"
maxUsers: integer("max_users").notNull().default(10),
scene: jsonb("scene").$type<SceneItem[]>().notNull().default([]),
sceneHash: text("scene_hash").notNull().default(""),
closed: boolean("closed").notNull().default(false),
createdAt: createdAt(),
updatedAt: updatedAt(),
},
(t) => [index("hosted_room_owner").on(t.ownerId), index("hosted_room_created").on(t.createdAt)],
);