← back to Animals
migrations/017_lost_pet_alerts.sql
45 lines
-- Project: Animals — lost-pet alert opt-in + dispatch dedupe.
--
-- Wires src/lib/lost_pet_alert.js → fires when a marketplace listing of
-- listing_type='lost_pet' is posted. Recipients are app_users who:
-- 1. Set alert_lost_pets = TRUE (must opt in — default FALSE)
-- 2. Have a home_zip matching listing.zip OR same ZIP-3 prefix
-- (sectional center facility — typically dozens of cities)
-- 3. Have a verified email and have not unsubscribed entirely
--
-- SMS — alert_lost_pets_sms exists for future TCPA-compliant double opt-in
-- flow, but no sender code references it. Per Steve's standing rule: no SMS
-- without an explicit opt-in event we can prove (timestamp + source + IP).
--
-- Dedupe: lost_pet_alerts_sent is a (listing_id, app_user_id, channel) record
-- so re-bumping a listing does not re-blast the neighborhood.
BEGIN;
ALTER TABLE app_users
ADD COLUMN IF NOT EXISTS alert_lost_pets BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS alert_lost_pets_sms BOOLEAN NOT NULL DEFAULT FALSE;
-- Same ZIP: idx_app_users_zip (from migration 003) already covers home_zip = $1.
-- ZIP-3 prefix lookup wants a partial expression index so we don't full-scan
-- app_users every time someone posts a lost pet.
CREATE INDEX IF NOT EXISTS idx_app_users_zip3_alerts
ON app_users (LEFT(home_zip, 3))
WHERE alert_lost_pets = TRUE AND home_zip IS NOT NULL;
-- Per-recipient send ledger. Channel column lets us add 'sms' later without
-- a schema change. UNIQUE prevents duplicate sends if a worker retries.
CREATE TABLE IF NOT EXISTS lost_pet_alerts_sent (
listing_id BIGINT NOT NULL REFERENCES marketplace_listings(id) ON DELETE CASCADE,
app_user_id BIGINT NOT NULL REFERENCES app_users(id) ON DELETE CASCADE,
channel TEXT NOT NULL CHECK (channel IN ('email','sms')),
sent_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
status TEXT NOT NULL DEFAULT 'sent' CHECK (status IN ('sent','failed','skipped')),
error TEXT,
PRIMARY KEY (listing_id, app_user_id, channel)
);
CREATE INDEX IF NOT EXISTS idx_lost_pet_alerts_listing ON lost_pet_alerts_sent(listing_id);
CREATE INDEX IF NOT EXISTS idx_lost_pet_alerts_user ON lost_pet_alerts_sent(app_user_id);
COMMIT;