Data model
Drape's primary datastore is MongoDB (accessed with motor async +
pymongo). Roughly two dozen collections are in use; the ones that carry the
product are below. Try-on jobs are not a collection — they live in Redis with
a short TTL.
Core relationships
Collections
| Collection | Purpose | Notable fields |
|---|---|---|
users | Accounts + fit profile + body photo | id, email (unique), password_hash, role, is_admin, is_store_admin, consent fields, referral fields, account_status |
products | Catalog items | id, store_id, garment_style (drives AI routing), image_url, color_variants[], sizes_available, fashn_category |
stores | Brands / white-label storefronts | id, slug (unique), plan_type, subscription_status, Stripe Connect fields, BYOK API keys |
orders | Marketplace orders | id, stripe_session_id (unique), amount / platform_fee / net_amount (cents), status, product_snapshot |
tryon_history | Per-user render history | user + product + result references |
tryon_observations | Quality/rating records | mirrored to a Supabase table of the same name |
body_references | ANSUR II / sizing reference data | no PII |
render_logs | Per-render audit trail | one doc per render attempt |
deletion_log | BIPA deletion audit | two-phase deletion_confirmed flag |
Others in use include training_events, previews, saved_items,
saved_looks, outfits, brand_follows, drop_waitlist, brand_waitlist,
onboarding_submissions, reset_tokens, verify_tokens, invite_codes, and
bug_reports. Indexes are created at startup.
The body-photo fields
The most important — and most sensitive — data hangs off the user document.
These fields are written by the pipeline (not declared in the Pydantic model), and
body_photo_data is the single source of truth for try-on eligibility.
| Field | Meaning |
|---|---|
body_photo_data | Base64 composited JPEG — what try-on actually uses |
body_photo_hash | 16-char hash; part of the render cache key |
body_photo_owner_id | BIPA ownership stamp; mismatch blocks try-on with 409 |
body_photo_original | Supabase path to the original upload |
body_photo_cutout | Background-removed cutout path |
body_photo_mask_upper_url / body_photo_mask_lower_url | EVF-SAM garment masks |
body_photo_quality_flags / body_photo_needs_review | Quality signals |
analyze-body-photos and body-photo/upload both write overlapping body-photo
fields (a dual-write pattern with a TODO in the code). Consolidation is
deferred — see tech debt.
Why jobs live in Redis, not Mongo
A try-on job is ephemeral coordination state, not a record of truth. It is stored
as `job:{job_id}` in Redis with a 10-minute to 1-hour TTL, carrying
{status, user_id}. The user_id is what the status endpoint checks for
ownership. The durable audit of a render is the render_logs Mongo document; the
job key is allowed to expire.
Storage (Supabase)
Body photos and garment images are objects, not documents, so they live in
Supabase Storage — storage only, no Supabase Auth. The biometric buckets
(body-photos-original, body-photos-cutout, body-photos-mask) are private
and accessed via server-side signed URLs; garment buckets are public. All access
uses the service-role key, so authorization is enforced in FastAPI, not by
Supabase RLS. See security for the full model.