Prisma models¶
The canonical source is prisma/schema.prisma. This page is the human-readable reference. For the why behind the shape, see Architecture → Data model.
Enum: Category¶
Used as Product.category. LAPTOP and PREBUILT are reserved for #24 and #25; not in active use in M1.
Retailer¶
| Field | Type | Notes |
|---|---|---|
id |
String (uuid) |
Primary key |
name |
String (unique) |
Human-readable retailer name (e.g., "PCAndParts") |
slug |
String (unique) |
URL slug (e.g., pcandparts) |
domain |
String |
Canonical domain (e.g., pcandparts.com) |
scraperId |
String (unique) |
Matches the file in src/scrapers/sites/, e.g., pcandparts for pcandparts.ts |
active |
Boolean (default true) |
Soft on/off switch — inactive retailers are skipped by the scraper runner |
createdAt |
DateTime |
Set on insert |
updatedAt |
DateTime |
Set by Prisma on update |
Relations: listings: Listing[], clicks: Click[].
Product¶
The canonical SKU. Independent of any retailer.
| Field | Type | Notes |
|---|---|---|
id |
String (uuid) |
Primary key |
category |
Category (enum) |
One of the values above |
brand |
String |
E.g., AMD, NVIDIA, Corsair |
model |
String |
E.g., Ryzen 7 7800X3D, RM850x |
slug |
String (unique) |
URL slug, e.g., amd-ryzen-7-7800x3d |
cpuSocket |
String? |
E.g., AM5, LGA1700. Used for compat checks. |
ramType |
String? |
E.g., DDR4, DDR5 |
ramSpeedMhz |
Int? |
E.g., 6000 |
formFactor |
String? |
E.g., ATX, mATX, ITX |
tdpWatts |
Int? |
Used for PSU wattage compat check |
lengthMm |
Int? |
GPU length, used for case clearance check |
heightMm |
Int? |
Cooler height, used for case max-cooler-height check |
psuWattage |
Int? |
For PSU products |
specs |
Json (default {}) |
Free-form spec blob — anything not promoted to a typed column |
imageUrl |
String? |
Backfilled by the scraper pipeline; first scraper to find an image wins |
releaseDate |
DateTime? |
Optional, for sorting by recency |
msrpUsd |
Decimal(10,2)? |
Manufacturer's suggested retail; reference only |
createdAt |
DateTime |
|
updatedAt |
DateTime |
Indexes: category, (category, cpuSocket), (brand, model).
Relations: listings: Listing[].
Listing¶
One retailer's offer for a Product (or an unmatched scrape).
| Field | Type | Notes |
|---|---|---|
id |
String (uuid) |
Primary key |
retailerId |
String |
FK → Retailer |
productId |
String? |
FK → Product. Nullable — set only after matcher resolves |
url |
String |
Full URL to the retailer's product page |
retailerSku |
String? |
Retailer's own SKU, when available |
titleRaw |
String |
Original listing title as scraped — used for matcher debugging and re-runs |
matchConfidence |
Float? |
0-1, set by src/lib/matching.ts |
matchStatus |
String (default "unmatched") |
One of unmatched, weak, matched, manual |
lastSeenAt |
DateTime (default now) |
Updated on every scrape — drives staleness detection |
createdAt |
DateTime |
|
updatedAt |
DateTime |
Constraints: @@unique([retailerId, url]) — one Listing per retailer URL, ever.
Indexes: productId, matchStatus.
Relations: retailer: Retailer, product: Product?, prices: ListingPrice[], clicks: Click[].
ListingPrice¶
Append-only price snapshot. New row per scrape.
| Field | Type | Notes |
|---|---|---|
id |
String (uuid) |
Primary key |
listingId |
String |
FK → Listing, cascades on delete |
priceUsd |
Decimal(10,2)? |
Null = "Quote-only" / no public price (common on 961Souq) |
inStock |
Boolean (default true) |
Drives the Listing's effective Active / OutOfStock state |
scrapedAt |
DateTime (default now) |
The moment the price snapshot was taken |
Indexes: (listingId, scrapedAt DESC) — supports "latest price" queries.
Append-only invariant: never updated, only inserted. The history is the source of truth. See Listing lifecycle.
Click¶
Outbound deep-link tracking. Written when a user clicks "Buy" on a Listing (UC-2).
| Field | Type | Notes |
|---|---|---|
id |
String (uuid) |
Primary key |
clickToken |
String (unique) |
Random token, used for affiliate postback (#17) |
retailerId |
String |
FK → Retailer |
listingId |
String |
FK → Listing |
source |
String |
Where the click originated: product-detail, build-cart, etc. |
ipHash |
String? |
Salted hash of source IP, for affiliate fraud detection. Salt = IP_HASH_SECRET env var |
ua |
String? |
User-Agent, for analytics |
referrer |
String? |
HTTP Referer, where available |
createdAt |
DateTime |
Indexes: (retailerId, createdAt DESC), (listingId, createdAt DESC).
Notable absences¶
These don't exist yet — they're part of M2/M3 scope: