diff --git a/schema-example.sql b/schema-example.sql deleted file mode 100644 index 62f261e..0000000 --- a/schema-example.sql +++ /dev/null @@ -1,190 +0,0 @@ --- ============================================ --- Gunbuilder / AvantLink Schema DDL --- ============================================ - --- Enable extension for UUID generation (if not already enabled) -CREATE EXTENSION IF NOT EXISTS pgcrypto; - --- ============================================ --- 1. merchants --- ============================================ -CREATE TABLE merchants ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - avantlink_mid TEXT NOT NULL UNIQUE, -- AvantLink merchant ID - feed_url TEXT NOT NULL, -- Product datafeed URL - is_active BOOLEAN NOT NULL DEFAULT TRUE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_merchants_active ON merchants (is_active); - - --- ============================================ --- 2. part_categories --- Your internal Gunbuilder categories --- ============================================ -CREATE TABLE part_categories ( - id SERIAL PRIMARY KEY, - slug TEXT NOT NULL UNIQUE, -- 'lower', 'upper', 'barrel', etc. - name TEXT NOT NULL, -- 'Lower Receiver' - description TEXT -); - - --- ============================================ --- 3. brands --- ============================================ -CREATE TABLE brands ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL UNIQUE, -- 'Aero Precision' - website TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - - --- ============================================ --- 4. products --- Canonical parts, independent of merchants --- ============================================ -CREATE TABLE products ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - brand_id INTEGER REFERENCES brands(id), - part_category_id INTEGER NOT NULL REFERENCES part_categories(id), - - name TEXT NOT NULL, - slug TEXT NOT NULL UNIQUE, - description TEXT, - caliber TEXT, - barrel_length_mm INTEGER, - gas_system TEXT, - handguard_length_mm INTEGER, - image_url TEXT, - spec_sheet_url TEXT, - - msrp NUMERIC(10,2), - current_lowest_price NUMERIC(10,2), - current_lowest_merchant_id INTEGER REFERENCES merchants(id), - - is_active BOOLEAN NOT NULL DEFAULT TRUE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_products_category ON products (part_category_id); -CREATE INDEX idx_products_brand ON products (brand_id); -CREATE INDEX idx_products_active ON products (is_active); -CREATE INDEX idx_products_lowest_price ON products (current_lowest_price); - - --- Optional: a "natural-ish" uniqueness constraint if you want it --- (Can be relaxed later if needed) -CREATE UNIQUE INDEX uniq_products_brand_name_cat -ON products (brand_id, name, part_category_id); - - --- ============================================ --- 5. product_offers --- Merchant-specific offers for a product --- ============================================ -CREATE TABLE product_offers ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - product_id UUID NOT NULL REFERENCES products(id) ON DELETE CASCADE, - merchant_id INTEGER NOT NULL REFERENCES merchants(id), - avantlink_product_id TEXT NOT NULL, - sku TEXT, - upc TEXT, - buy_url TEXT NOT NULL, - - price NUMERIC(10,2) NOT NULL, - original_price NUMERIC(10,2), - currency TEXT NOT NULL DEFAULT 'USD', - in_stock BOOLEAN NOT NULL DEFAULT TRUE, - - last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - first_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - --- Each merchant + avantlink_product_id combination must be unique -CREATE UNIQUE INDEX uniq_product_offers_merchant_product -ON product_offers (merchant_id, avantlink_product_id); - -CREATE INDEX idx_product_offers_product ON product_offers (product_id); -CREATE INDEX idx_product_offers_merchant ON product_offers (merchant_id); -CREATE INDEX idx_product_offers_in_stock ON product_offers (in_stock); -CREATE INDEX idx_product_offers_price ON product_offers (price); - - --- ============================================ --- 6. price_history --- For tracking price changes over time --- ============================================ -CREATE TABLE price_history ( - id BIGSERIAL PRIMARY KEY, - product_offer_id UUID NOT NULL REFERENCES product_offers(id) ON DELETE CASCADE, - price NUMERIC(10,2) NOT NULL, - recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_price_history_offer ON price_history (product_offer_id); -CREATE INDEX idx_price_history_recorded_at ON price_history (recorded_at); - - --- ============================================ --- 7. category_mappings --- Map raw AvantLink categories to internal part_categories --- ============================================ -CREATE TABLE category_mappings ( - id SERIAL PRIMARY KEY, - merchant_id INTEGER NOT NULL REFERENCES merchants(id), - raw_category_path TEXT NOT NULL, -- e.g. 'Firearms > AR-15 Parts > Lower Receivers' - part_category_id INTEGER NOT NULL REFERENCES part_categories(id), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - - UNIQUE (merchant_id, raw_category_path) -); - -CREATE INDEX idx_category_mappings_merchant ON category_mappings (merchant_id); -CREATE INDEX idx_category_mappings_part_category ON category_mappings (part_category_id); - - --- ============================================ --- 8. feed_imports --- Track each feed import run for observability --- ============================================ -CREATE TABLE feed_imports ( - id BIGSERIAL PRIMARY KEY, - merchant_id INTEGER NOT NULL REFERENCES merchants(id), - started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - finished_at TIMESTAMPTZ, - rows_total INTEGER, - rows_processed INTEGER, - rows_new INTEGER, - rows_updated INTEGER, - status TEXT NOT NULL DEFAULT 'running', -- 'running','success','error' - error_message TEXT -); - -CREATE INDEX idx_feed_imports_merchant ON feed_imports (merchant_id); -CREATE INDEX idx_feed_imports_status ON feed_imports (status); -CREATE INDEX idx_feed_imports_started_at ON feed_imports (started_at); - - --- ============================================ --- 9. Helpful seed data (optional) --- ============================================ - --- Part categories seed (adjust as needed) -INSERT INTO part_categories (slug, name) VALUES - ('lower', 'Lower Receiver'), - ('upper', 'Upper Receiver'), - ('barrel', 'Barrel'), - ('handguard', 'Handguard'), - ('stock', 'Stock'), - ('optic', 'Optic') -ON CONFLICT (slug) DO NOTHING; - --- Example merchant skeleton (fill real datafeed URL + AvantLink MID) --- INSERT INTO merchants (name, avantlink_mid, feed_url) --- VALUES ('Example Merchant', '12345', 'https://www.avantlink.com/api/...'); \ No newline at end of file