Data model
A condensed reference of the main Postgres tables. The authoritative source is server/migrations/.
Catalogue layer
Table
Purpose
Key fields
figures
The shared catalogue of figurines
name, figure_type, manufacturer_id, release_date, msrp_amount, is_nsfw, slug
manufacturers
Companies (Good Smile, Alter, …)
name, slug
series
Anime / game / manga series
name, slug
characters
Character names
name, slug
sculptors
Sculptor credits
name, slug
figure_series
Many-to-many figure ↔ series
figure_id, series_id
figure_characters
Many-to-many figure ↔ character
figure_id, character_id
figure_photos
Shared catalogue photos
figure_id, storage_key, is_primary, position
User layer
Table
Purpose
Key fields
users
User accounts
username, email, password_hash, is_admin, nsfw_visibility, preferred_currency, public_profile_show_nsfw
owned_items
Personal collection rows
figure_id, condition, price_amount, price_currency, shipping_amount, price_fx_rate , value_amount, value_currency, purchase_date, store_id, cover_photo_id, cover_scan_id, sort_order, archived_at
photos
Personal photos
owned_item_id, storage_key, position
scans
360° turntable frames
owned_item_id, storage_keys (array)
wishlist_items
"Want to own" list
figure_id, max_price_amount, max_price_currency, note
Money columns always come in pairs — NUMERIC(12,2) amount + CHAR(3) ISO
currency — and store the original amount; display conversion is a reading
layer (Money & currencies ). price_fx_rate
(also on preorders) is the currency → EUR rate frozen when the cost was
recorded , powering the drift-free plus-value; NULL (pre-v0.23 rows) falls
back to today's rate.
Pre-order lifecycle
Table
Purpose
Key fields
preorders
Pre-order rows tied to an owned_item
owned_item_id (unique), status, release_date_original, release_date_current, tracking_url, price_amount, price_currency, price_fx_rate, deposit_amount, deposit_refund_amount, shipped_at, estimated_delivery_days
preorder_date_history
Slip log
preorder_id, previous_date, new_date, source, note
Market prices
Table
Purpose
Key fields
figure_provider_prices
Latest fetched market price per figure (overwritten each sweep)
figure_id (PK), amount, currency, source, matched_version, fetched_at
figure_price_history
Append-only price change points (only when the price moves)
figure_id, amount, currency, source, matched_version, recorded_at
Both are filled by the admin-scheduled price sweep ;
the history drives the sparklines + evolution charts on
La Cote .
Operations
Table
Purpose
Key fields
app_settings
Live instance policies (no restart)
key (gsplat.creation_policy, cote.price_cron), value
server_job_runs
Historized scheduled-job runs (30 kept per job)
job_name, triggered_by, state, result (JSONB), error_message, started_at, finished_at
external_lookups
TTL cache for external fetches (FX rates, scrapes)
provider, lookup_key, payload, fetched_at
stores
Boutique registry (slug-deduped, user-created)
name, slug
Notifications
Table
Purpose
Key fields
notifications
Per-user notification log
user_id, event_type, payload (JSONB), read_at
notification_subscriptions
Per-channel routing
user_id, channel_type, event_type, enabled
notification_channels
System-level channel configuration
channel_type, enabled (admin toggle)
notification_dedup
Prevents double-fires
user_id, dedup_key, created_at
web_push_subscriptions
VAPID subscriptions
user_id, endpoint, keys
MangaCollector synergy
Table
Purpose
Key fields
manga_servers
Admin-curated allow-list of MangaCollector origins
base_url (unique, normalized origin), status (pending / approved / revoked), submitted_by, reviewed_by, reviewed_at, note
The link itself lives on users — manga_server_id (→ manga_servers) + manga_slug. The join to the catalogue is series.mal_id (populated from the AniList idMal), so a figure's series and a MangaCollector library entry line up with no manual mapping. Cross-link fetches only fire when the linked server is approved.
Activity + achievements
Table
Purpose
Key fields
activity_events
Audit log for the user feed and stats
user_id, kind, payload, created_at
achievements
Unlocked achievements per user
user_id, code, tier, unlocked_at
Auth
Table
Purpose
oidc_identities
Federated identity links (Google, generic)
tower_sessions
Server-side session storage
Migrations
All migrations live in server/migrations/ as plain .sql files, wrapped by server/src/migration/m2026*_*.rs modules. The SeaORM MigratorTrait runs them in lexical order on backend boot.
Idempotent by design (IF NOT EXISTS, OR REPLACE), so a restart is always safe.