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

+19
View File
@@ -0,0 +1,19 @@
CREATE TABLE "hosted_room" (
"n" serial PRIMARY KEY NOT NULL,
"owner_id" text NOT NULL,
"owner_imvu_id" bigint NOT NULL,
"name" text NOT NULL,
"description" text DEFAULT '' NOT NULL,
"room_pid" bigint NOT NULL,
"rating" text DEFAULT 'general' NOT NULL,
"max_users" integer DEFAULT 10 NOT NULL,
"scene" jsonb DEFAULT '[]'::jsonb NOT NULL,
"scene_hash" text DEFAULT '' NOT NULL,
"closed" boolean DEFAULT false NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "hosted_room" ADD CONSTRAINT "hosted_room_owner_id_user_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "hosted_room_owner" ON "hosted_room" USING btree ("owner_id");--> statement-breakpoint
CREATE INDEX "hosted_room_created" ON "hosted_room" USING btree ("created_at");
File diff suppressed because it is too large. Load diff
+7
View File
@@ -36,6 +36,13 @@
"when": 1793347080470,
"tag": "0004_parent_info",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1793355138984,
"tag": "0005_hosted_rooms",
"breakpoints": true
}
]
}
+1 -1
View File
@@ -11,7 +11,7 @@ import { fileURLToPath } from "node:url";
import * as schema from "./schema";
export { schema };
export type { FileEntry, ParentInfo } from "./schema";
export type { FileEntry, ParentInfo, SceneItem } from "./schema";
export * from "drizzle-orm";
const migrationsFolder = fileURLToPath(new URL("../drizzle", import.meta.url));
+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)],
);