-- Subscriptions: a logged-in user follows someone else's shared list or a -- single shared item, to receive the same price-drop emails the owner gets. -- Exactly one of list_id / item_id is set per row. A list subscription -- implicitly covers every item on that list — present and future — expanded -- at notify time rather than materialised per item. CREATE TABLE subscriptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, list_id UUID REFERENCES lists(id) ON DELETE CASCADE, item_id UUID REFERENCES items(id) ON DELETE CASCADE, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT subscription_target_exactly_one CHECK ((list_id IS NOT NULL) <> (item_id IS NOT NULL)) ); -- One subscription per (user, list) and per (user, item). CREATE UNIQUE INDEX subscriptions_user_list ON subscriptions (user_id, list_id) WHERE list_id IS NOT NULL; CREATE UNIQUE INDEX subscriptions_user_item ON subscriptions (user_id, item_id) WHERE item_id IS NOT NULL; -- Per-subscriber, per-item de-dupe latch for target-price alerts — the -- subscriber-side mirror of items.notified_at (which latches the owner). -- Present = already announced this drop; absent = armed. Cleared when the -- price climbs back above the item's target. CREATE TABLE subscription_notify ( user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, item_id UUID NOT NULL REFERENCES items(id) ON DELETE CASCADE, notified_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (user_id, item_id) );