T5 products placed in ordinary IMVU rooms, kept per room instance
(room_decoration, migration 0006). GET /api/v1/decorations/:instance (and
?instances= for up to 20) gives what the viewer may see, with a hash;
PUT is for the room owner's T5DE person only, checks the products exist,
and tells every realtime session with a chat in that room ({"type":
"decor"}). Hosted rooms' IDs are refused.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
2a1fc94df6
commit
f65f36c1ea
12 files changed
+2325
-6
No files matched your search
+51
-1
@@ -12,6 +12,7 @@ import { isAdultRoom, Rooms } from "./rooms";
|
||||
import { Signer } from "./keys";
|
||||
import { ProductError, Products } from "./products";
|
||||
import { chatId, HostedError, HostedRooms, instanceId, maxPlacement, maxSceneItems } from "./hosted";
|
||||
import { DecorationError, Decorations, maxBatch, maxDecorationPlacement, maxDecorations } from "./decorations";
|
||||
|
||||
const { user, session, imvuLink, linkChallenge, audit } = schema;
|
||||
|
||||
@@ -80,6 +81,7 @@ export function createApp({ db, auth, imvu, realtime: rt, age: ageService, rooms
|
||||
const products = new Products(db, signer, imvu);
|
||||
const rooms = roomsService ?? new Rooms(db, imvu);
|
||||
const hosted = new HostedRooms(db);
|
||||
const decorations = new Decorations(db);
|
||||
const realtime =
|
||||
rt ??
|
||||
new Realtime({
|
||||
@@ -248,6 +250,22 @@ export function createApp({ db, auth, imvu, realtime: rt, age: ageService, rooms
|
||||
t.String({ maxLength: maxPlacement }),
|
||||
]);
|
||||
|
||||
/** Runs a decorations operation, turning its errors into Problems. */
|
||||
const decorationCall = async <T>(f: () => Promise<T>): Promise<T> => {
|
||||
try {
|
||||
return await f();
|
||||
} catch (e) {
|
||||
if (e instanceof DecorationError) throw new Problem(e.status, e.slug, e.message);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
const decorationItem = t.Tuple([
|
||||
t.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }),
|
||||
t.String({ pattern: "^[a-z2-7]{10}$" }),
|
||||
t.String({ maxLength: maxDecorationPlacement }),
|
||||
]);
|
||||
|
||||
const productParam = t.Object({ id: t.String({ pattern: "^[a-z2-7]{10}$" }) });
|
||||
|
||||
const ownLink = async (p: Person, imvuId: number) => {
|
||||
@@ -689,6 +707,38 @@ export function createApp({ db, auth, imvu, realtime: rt, age: ageService, rooms
|
||||
{ signedIn: true, params: hostedParam },
|
||||
)
|
||||
|
||||
// Decorations over IMVU rooms (docs/products.md, "Decorations").
|
||||
.get(
|
||||
"/decorations",
|
||||
async ({ person, query }) => {
|
||||
const instances = [...new Set((query.instances ?? "").split(",").filter(Boolean))];
|
||||
if (instances.length > maxBatch) throw new Problem(400, "invalid", `Up to ${maxBatch} rooms at a time.`);
|
||||
return { rooms: await decorationCall(async () => decorations.get(instances, await personAdult(person))) };
|
||||
},
|
||||
{ query: t.Object({ instances: t.Optional(t.String({ maxLength: 1024 })) }) },
|
||||
)
|
||||
.get(
|
||||
"/decorations/:instance",
|
||||
async ({ person, params }) =>
|
||||
(await decorationCall(async () => decorations.get([params.instance], await personAdult(person))))[0]!,
|
||||
{ params: t.Object({ instance: t.String({ maxLength: 32 }) }) },
|
||||
)
|
||||
.put(
|
||||
"/decorations/:instance",
|
||||
async ({ me, params, body }) => {
|
||||
if (!limiter.allow(`decor:${me.id}`, 60, 60 * 1000))
|
||||
throw new Problem(429, "rate_limited", "Too many changes. Try again in a moment.");
|
||||
await decorationCall(() => decorations.set(me.id, params.instance, body.contents));
|
||||
realtime.decorated(params.instance);
|
||||
return (await decorations.get([params.instance], await personAdult(me)))[0]!;
|
||||
},
|
||||
{
|
||||
signedIn: true,
|
||||
params: t.Object({ instance: t.String({ maxLength: 32 }) }),
|
||||
body: t.Object({ contents: t.Array(decorationItem, { maxItems: maxDecorations }) }),
|
||||
},
|
||||
)
|
||||
|
||||
// Catalogs: registering one, and the directory.
|
||||
.post(
|
||||
"/catalogs",
|
||||
@@ -1139,7 +1189,7 @@ export function createApp({ db, auth, imvu, realtime: rt, age: ageService, rooms
|
||||
.get("/health", () => ({ ok: true }))
|
||||
.use(api);
|
||||
|
||||
return { app, realtime, age, rooms, products, signer, hosted };
|
||||
return { app, realtime, age, rooms, products, signer, hosted, decorations };
|
||||
}
|
||||
|
||||
export type App = ReturnType<typeof createApp>["app"];
|
||||
Reference in new issue
Block a user