Files
t5de-hub/packages/db/src/schema.ts
T
dhkatzandClaude Opus 5.5 6ba4d83317
CI / test (push) Successful in 6m38s
Room channel and rooms list
The realtime connection now carries the IMVU chats a client is in: the hub tells each proxy in a chat which
other T5DE users are there and what community products they wear (adult products only to adult accounts), on
joining and on every change. Rooms T5DE users enter are looked up on IMVU; those that are public, listed and
whose owner turned on "List my public rooms" appear on the new Rooms page, with the T5DE users in them who
show they're online. AP rooms are shown to adults only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 18:14:43 -07:00

224 lines
8.2 KiB
TypeScript

// The hub's tables: Better Auth's (people, sessions, passwords, signing keys, device sign-ins) and the hub's own
// (linked IMVU accounts, link codes, the audit log). Better Auth's are as its CLI generates them for the plugins
// used (username, admin, device-authorization, bearer, jwt), with the hub's linkCap added to user.
import { sql } from "drizzle-orm";
import {
bigint,
boolean,
index,
integer,
jsonb,
pgTable,
primaryKey,
serial,
text,
timestamp,
uniqueIndex,
} from "drizzle-orm/pg-core";
const createdAt = () => timestamp("created_at", { withTimezone: true }).notNull().defaultNow();
const updatedAt = () =>
timestamp("updated_at", { withTimezone: true })
.notNull()
.defaultNow()
.$onUpdate(() => new Date());
// A person: one T5DE account.
export const user = pgTable(
"user",
{
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").notNull().default(false),
image: text("image"),
createdAt: createdAt(),
updatedAt: updatedAt(),
username: text("username").unique(),
displayUsername: text("display_username"),
role: text("role").notNull().default("user"),
banned: boolean("banned").default(false),
banReason: text("ban_reason"),
banExpires: timestamp("ban_expires", { withTimezone: true }),
// How many IMVU accounts the person may link; moderators raise it on request.
linkCap: integer("link_cap").notNull().default(10),
},
(t) => [uniqueIndex("user_username_lower").on(sql`lower(${t.username})`)],
);
export const session = pgTable(
"session",
{
id: text("id").primaryKey(),
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
token: text("token").notNull().unique(),
createdAt: createdAt(),
updatedAt: updatedAt(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
impersonatedBy: text("impersonated_by"),
},
(t) => [index("session_user").on(t.userId)],
);
export const account = pgTable(
"account",
{
id: text("id").primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: timestamp("access_token_expires_at", { withTimezone: true }),
refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { withTimezone: true }),
scope: text("scope"),
password: text("password"),
createdAt: createdAt(),
updatedAt: updatedAt(),
},
(t) => [index("account_user").on(t.userId)],
);
export const verification = pgTable(
"verification",
{
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
createdAt: createdAt(),
updatedAt: updatedAt(),
},
(t) => [index("verification_identifier").on(t.identifier)],
);
export const jwks = pgTable("jwks", {
id: text("id").primaryKey(),
publicKey: text("public_key").notNull(),
privateKey: text("private_key").notNull(),
createdAt: createdAt(),
expiresAt: timestamp("expires_at", { withTimezone: true }),
alg: text("alg"),
crv: text("crv"),
});
// A T5DE proxy signing in: it shows userCode, the person approves it on the website.
export const deviceCode = pgTable("device_code", {
id: text("id").primaryKey(),
deviceCode: text("device_code").notNull(),
userCode: text("user_code").notNull(),
userId: text("user_id").references(() => user.id, { onDelete: "cascade" }),
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
status: text("status").notNull(),
lastPolledAt: timestamp("last_polled_at", { withTimezone: true }),
pollingInterval: integer("polling_interval"),
clientId: text("client_id"),
scope: text("scope"),
createdAt: createdAt(),
updatedAt: updatedAt(),
});
// A linked IMVU account. Linking it again from another person moves it (the most recent proof wins).
export const imvuLink = pgTable(
"imvu_link",
{
imvuId: bigint("imvu_id", { mode: "number" }).primaryKey(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
// What catalogs and rooms see instead of the person, so a person's accounts can't be connected.
pairwiseId: text("pairwise_id").notNull().unique(),
username: text("username").notNull(),
linkedAt: timestamp("linked_at", { withTimezone: true }).notNull().defaultNow(),
showBadge: boolean("show_badge").notNull().default(false),
showPresence: boolean("show_presence").notNull().default(false),
listRooms: boolean("list_rooms").notNull().default(false),
},
(t) => [index("imvu_link_user").on(t.userId)],
);
// An IMVU room a T5DE user entered whose owner is a linked account, as IMVU last described it. Listed on the hub
// only if its owner chose to list their rooms and IMVU says it's public and listed.
export const room = pgTable(
"room",
{
instanceId: text("instance_id").primaryKey(), // "<owner>-<n>"
ownerImvuId: bigint("owner_imvu_id", { mode: "number" }).notNull(),
name: text("name").notNull(),
description: text("description").notNull().default(""),
imageURL: text("image_url").notNull().default(""),
privacy: text("privacy").notNull(),
type: text("type").notNull(),
contentRating: text("content_rating").notNull().default(""),
accessPass: boolean("access_pass").notNull().default(false),
occupancy: integer("occupancy").notNull().default(0),
capacity: integer("capacity").notNull().default(0),
expired: boolean("expired").notNull().default(false),
checkedAt: timestamp("checked_at", { withTimezone: true }).notNull().defaultNow(),
seenAt: timestamp("seen_at", { withTimezone: true }).notNull().defaultNow(),
},
(t) => [index("room_owner").on(t.ownerImvuId)],
);
// An age check by one provider: of a person, or of one of their linked IMVU accounts. Only the outcome: never a
// birth date, document, photo or name (providers that see those keep them).
export const ageCheck = pgTable(
"age_check",
{
id: text("id").primaryKey(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
// Set when the check is of one IMVU account (IMVU's own verification); null when it's of the person.
imvuId: bigint("imvu_id", { mode: "number" }),
provider: text("provider").notNull(), // "imvu", "didit", "wallet", ...
method: text("method").notNull().default(""), // e.g. "imvu_age_verified", "id_document", "wallet_mdoc"
status: text("status").notNull(), // "pending", "complete", "failed", "revoked"
overEighteen: boolean("over_eighteen").notNull().default(false),
providerRef: text("provider_ref"), // the provider's session or transaction ID
createdAt: createdAt(),
checkedAt: timestamp("checked_at", { withTimezone: true }),
expiresAt: timestamp("expires_at", { withTimezone: true }),
},
(t) => [
index("age_check_user").on(t.userId),
index("age_check_imvu").on(t.imvuId),
uniqueIndex("age_check_provider_ref").on(t.provider, t.providerRef),
],
);
// A code the person is to put in an IMVU account's tagline, proving it's theirs.
export const linkChallenge = pgTable(
"link_challenge",
{
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
imvuId: bigint("imvu_id", { mode: "number" }).notNull(),
username: text("username").notNull(),
code: text("code").notNull(),
createdAt: createdAt(),
},
(t) => [primaryKey({ columns: [t.userId, t.imvuId] })],
);
export const audit = pgTable(
"audit",
{
id: serial("id").primaryKey(),
at: timestamp("at", { withTimezone: true }).notNull().defaultNow(),
actorId: text("actor_id").references(() => user.id, { onDelete: "set null" }),
action: text("action").notNull(),
target: text("target").notNull().default(""),
detail: jsonb("detail").$type<Record<string, unknown>>().notNull().default({}),
},
(t) => [index("audit_at").on(t.at)],
);