Products: carry the IMVU parent's image and categories
CI / test (push) Successful in 49s

Revisions with an IMVU parent store its public info (image, category path, gender)
from IMVU's product API, looked up after registering and in a periodic pass.
Product views gain parent, image and categories; GET /products takes category and
limit. The website shows the image on cards and product pages.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-30 01:03:02 -07:00
1 parent d57674d90c
commit 6668b55117
15 files changed
+2026 -29

No files matched your search

+21 -8
View File
@@ -2,7 +2,7 @@
// origin (Next.js rewrites /api to here), so session cookies are first-party; T5DE's proxy calls it with a bearer
// token.
import { Elysia, t } from "elysia";
import { and, desc, eq, ilike, inArray, ne, or, sql, count, schema, type Database } from "@t5de/db";
import { and, desc, eq, ilike, inArray, ne, or, sql, count, schema, type Database, type ParentInfo } from "@t5de/db";
import type { Auth } from "./auth";
import { AgeError, AgeService } from "./age";
import { imvuAgeProvider, imvuResult } from "./age/imvu";
@@ -76,7 +76,7 @@ class Limiter {
export function createApp({ db, auth, imvu, realtime: rt, age: ageService, rooms: roomsService, apiURL }: Deps) {
const age = ageService ?? new AgeService(db, [imvuAgeProvider(imvu)]);
const signer = new Signer(db, apiURL ?? "http://localhost:4101");
const products = new Products(db, signer);
const products = new Products(db, signer, imvu);
const rooms = roomsService ?? new Rooms(db, imvu);
const realtime =
rt ??
@@ -178,10 +178,12 @@ export function createApp({ db, auth, imvu, realtime: rt, age: ageService, rooms
}
};
/** `latest`: the latest revision's parent and its info (the image and categories are the parent's). */
const productView = (
p: typeof schema.product.$inferSelect,
creator?: string | null,
creatorUsername?: string | null,
latest?: { parent: number | null; parentInfo: ParentInfo | null } | null,
) => ({
id: p.id,
name: p.name,
@@ -190,6 +192,9 @@ export function createApp({ db, auth, imvu, realtime: rt, age: ageService, rooms
latest: p.latest,
creator: creator ?? creatorUsername ?? "",
creatorUsername: creatorUsername ?? "",
parent: latest?.parent ?? 0,
image: latest?.parentInfo?.image ?? null,
categories: latest?.parentInfo?.categories ?? [],
createdAt: p.createdAt.toISOString(),
updatedAt: p.updatedAt.toISOString(),
});
@@ -616,10 +621,17 @@ export function createApp({ db, auth, imvu, realtime: rt, age: ageService, rooms
"/products",
async ({ person, query }) => {
const adult = await personAdult(person);
const rows = await products.list({ q: query.q, creator: query.creator, adult });
return { products: rows.map((r) => productView(r.product, r.creator, r.creatorUsername)) };
const rows = await products.list({ ...query, adult });
return { products: rows.map((r) => productView(r.product, r.creator, r.creatorUsername, r)) };
},
{
query: t.Object({
q: t.Optional(t.String({ maxLength: 100 })),
creator: t.Optional(t.String({ maxLength: 64 })),
category: t.Optional(t.Numeric({ minimum: 1, maximum: 2_147_483_647, multipleOf: 1 })),
limit: t.Optional(t.Numeric({ minimum: 1, maximum: 200, multipleOf: 1 })),
}),
},
{ query: t.Object({ q: t.Optional(t.String({ maxLength: 100 })), creator: t.Optional(t.String({ maxLength: 64 })) }) },
)
.get(
"/products/:id",
@@ -627,8 +639,9 @@ export function createApp({ db, auth, imvu, realtime: rt, age: ageService, rooms
const adult = await personAdult(person);
const p = await productCall(() => products.get(params.id, adult));
const lib = person ? await products.inLibrary(person.id, params.id) : undefined;
const latest = p.revisions.find((r) => r.number === p.product.latest);
return {
product: productView(p.product, p.creator, p.creatorUsername),
product: productView(p.product, p.creator, p.creatorUsername, latest),
revisions: p.revisions.map((r) => ({
number: r.number,
rating: r.rating,
@@ -651,7 +664,7 @@ export function createApp({ db, auth, imvu, realtime: rt, age: ageService, rooms
async ({ me, params, body }) => {
const p = await productCall(() => products.update(me.id, params.id, body));
await audited(me.id, "product.edit", p.id, { fields: Object.keys(body) });
return { product: productView(p) };
return { product: productView(p, undefined, undefined, await products.latestParent(p)) };
},
{
signedIn: true,
@@ -704,7 +717,7 @@ export function createApp({ db, auth, imvu, realtime: rt, age: ageService, rooms
"/library",
async ({ me }) => ({
library: (await products.library(me.id)).map((l) => ({
product: productView(l.product, l.creator),
product: productView(l.product, l.creator, undefined, l),
stayOn: l.stayOn,
addedAt: l.addedAt.toISOString(),
})),