feat: establish auth listing and image vertical slices
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).
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
# Data Model
|
||||
|
||||
## Core relationship
|
||||
|
||||
```text
|
||||
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 (`PRODUCT` or `SEARCH`)
|
||||
- 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.
|
||||
Reference in New Issue
Block a user