Includes MFA-capable authentication, session and CSRF security, password recovery, listing lifecycle and ownership rules, inventory image metadata, local ImageStorage, migrations, tests, and implementation status documentation. Verification: npm test (62 passing) and integration suite (14 passing).
4.1 KiB
Data Model
Core relationship
User → Seller → Product → Inventory Item → Listing
│
├── Images
├── Acquisition
├── Pricing Observations
├── Market Valuations
└── Activity
Sale → Sale Items → Inventory Items
Product
Represents what the item is, independent of physical ownership.
Examples:
- Charizard ex — SV151 — #199/165
- Pokémon Center Worlds 2025 Pikachu Plush
Products persist after all inventory is sold.
Inventory Item
Represents one physical copy.
Each copy has its own:
- condition
- images
- acquisition
- asking price/listing
- market valuation
- notes
- status
Do not model multiple physical copies as a quantity field on one Inventory Item.
Listing
Represents how an Inventory Item is currently offered publicly.
A Product can have many physical Inventory Items with different conditions and asking prices.
Sale
A manual external transaction. One Sale can contain multiple Sale Items.
Provider Product Mapping
Provider mappings are Product-level relationships.
Recommended fields:
- id
- product_id
- provider_id
- mapping_type (
PRODUCTorSEARCH) - external_identifier
- search_query / search parameters
- source_url
- status
- metadata
- created_at
- updated_at
- disabled_at
A provider can support one or both mapping types.
eBay supports both:
- specific product/catalog mappings where appropriate
- search/query mappings where no canonical product is appropriate
Mappings can be replaced without destroying historical records.
Only one active/default mapping per provider should be used for normal pricing unless a provider integration explicitly supports multiple active sources.
Suggested pricing tables
pricing_providers
id, name, code, is_enabled, priority, configuration, created_at, updated_at
product_pricing_providers
product_id, provider_id, is_enabled
pricing_provider_mappings
As described above.
pricing_observations
id, product_id, inventory_item_id, provider_id, source_identifier, source_url, condition_id, price_minor, currency, observed_at, quality_score, is_outlier, outlier_reason, raw_reference
market_valuations
id, inventory_item_id, value_minor, currency, strategy, confidence, source_count, calculated_at
market_value_overrides
id, inventory_item_id, value_minor, currency, reason, created_by, created_at, removed_at
pricing_jobs
id, job_type, status, requested_by, started_at, completed_at, total_items, processed_items, successful_items, failed_items, error_summary, created_at
Conditions
condition_profiles:
- id
- category_id
- name
- active
- sort
conditions:
- id
- condition_profile_id
- name
- code
- quality_rank
- description
- active
- sort
Images
inventory_images:
- id
- inventory_item_id
- storage_key
- original_filename
- mime_type
- file_size
- width
- height
- alt_text
- sort_order
- is_primary
- processing_status
- created_at
- updated_at
Acquisitions
acquisitions:
- id
- inventory_item_id
- method
- cost_minor
- currency
- date
- source
- location
- shipping_minor
- tax_minor
- other_fees_minor
- estimated_trade_value_minor
- notes
- created_at
- updated_at
Sales
sales:
- id
- seller_id
- sale_date
- sale_location
- sold_price_minor
- currency
- platform_fees_minor
- payment_processing_fees_minor
- shipping_cost_minor
- taxes_minor
- other_fees_minor
- buyer_name
- buyer_contact
- notes
- created_by
- created_at
- updated_at
sale_items:
- id
- sale_id
- inventory_item_id
- sold_price_minor
- cost_basis_minor
Activity
Activity records retain:
- affected Product/Inventory Item
- event
- previous value where applicable
- new value
- actor
- timestamp
Security events are stored separately.
Money
Store money as integer minor units. Negative values are prohibited; zero is valid. USD is canonical for stored/calculated market values unless a documented external representation requires another currency.