-- Accounts (people), their linked IMVU accounts, website sessions, sign-ins from proxies, and the audit log. create table persons ( id bigserial primary key, username text not null, password_hash text not null, role text not null default 'user' check (role in ('user', 'moderator', 'admin')), -- How many IMVU accounts the person may link; moderators raise it on request. link_cap integer not null default 10 check (link_cap >= 0), created_at timestamptz not null default now(), banned_at timestamptz, ban_reason text not null default '' ); create unique index persons_username on persons (lower(username)); create table sessions ( token_hash bytea primary key, person_id bigint not null references persons on delete cascade, csrf text not null, created_at timestamptz not null default now(), expires_at timestamptz not null ); create index sessions_person on sessions (person_id); -- A linked IMVU account. Linking it again from another person moves it (the most recent proof wins). create table imvu_links ( imvu_id bigint primary key, person_id bigint not null references persons on delete cascade, -- What catalogs and rooms see instead of the person, so a person's accounts can't be connected. pairwise_id text not null unique, username text not null, linked_at timestamptz not null default now(), age_verified boolean not null default false, access_pass boolean not null default false, checked_at timestamptz not null default now(), show_badge boolean not null default false, show_presence boolean not null default false ); create index imvu_links_person on imvu_links (person_id); -- A code the person is to put in an IMVU account's tagline, proving it's theirs. create table link_challenges ( person_id bigint not null references persons on delete cascade, imvu_id bigint not null, username text not null, code text not null, created_at timestamptz not null default now(), primary key (person_id, imvu_id) ); -- Signing a proxy in: the proxy shows user_code, the person approves it on the website. create table device_codes ( device_hash bytea primary key, user_code text not null unique, created_at timestamptz not null default now(), expires_at timestamptz not null, person_id bigint references persons on delete cascade, consumed_at timestamptz ); create table refresh_tokens ( token_hash bytea primary key, person_id bigint not null references persons on delete cascade, created_at timestamptz not null default now(), last_used_at timestamptz not null default now(), revoked_at timestamptz ); create index refresh_tokens_person on refresh_tokens (person_id); create table audit ( id bigserial primary key, at timestamptz not null default now(), actor_id bigint references persons on delete set null, action text not null, target text not null default '', detail jsonb not null default '{}' ); create index audit_at on audit (at desc);