-- WhereWoof canonical schema (Phase 1) -- Source of truth until Laravel takes over migration ownership (Phase 4). -- Applied by: make db-up (frontend/cmd/migrate) CREATE TABLE IF NOT EXISTS users ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, email TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, name TEXT, phone TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS tags ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, tag_code TEXT NOT NULL UNIQUE, -- printed on QR + NFC, public identifier owner_id BIGINT REFERENCES users(id), status TEXT NOT NULL DEFAULT 'unset' CHECK (status IN ('unset', 'active', 'suspended')), item_type TEXT CHECK (item_type IN ('dog', 'cat', 'baggage', 'skis', 'other')), description TEXT, photo_url TEXT, phone TEXT, -- owner contact phone (shown via tel:) address TEXT, notes TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS scans ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, tag_id BIGINT NOT NULL REFERENCES tags(id), scanned_at TIMESTAMPTZ NOT NULL DEFAULT now(), lat DOUBLE PRECISION, lng DOUBLE PRECISION, location_shared BOOLEAN NOT NULL DEFAULT FALSE, scanner_phone TEXT, alert_sent BOOLEAN NOT NULL DEFAULT FALSE ); -- Phase 2: per-tag SMS alert control. Idempotent so existing DBs migrate cleanly. ALTER TABLE tags ADD COLUMN IF NOT EXISTS sms_enabled BOOLEAN NOT NULL DEFAULT TRUE; -- Phase 2.5: product templates + sales orders (mirrors 2014 products/online_orders model). CREATE TABLE IF NOT EXISTS products ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, sku TEXT NOT NULL UNIQUE, name TEXT NOT NULL, item_type TEXT CHECK (item_type IN ('dog', 'cat', 'baggage', 'skis', 'other')), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS orders ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, account_id BIGINT REFERENCES users(id), status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'paid', 'lapsed', 'cancelled')), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- Tag → product / order linkage (populated by the Laravel admin, Phase 4). ALTER TABLE tags ADD COLUMN IF NOT EXISTS product_id BIGINT REFERENCES products(id); ALTER TABLE tags ADD COLUMN IF NOT EXISTS order_id BIGINT REFERENCES orders(id); -- Phase 2.5: per-device anti-spam fingerprint on scans (idempotent). ALTER TABLE scans ADD COLUMN IF NOT EXISTS fingerprint TEXT; -- Phase 4: admin flag on users (idempotent). ALTER TABLE users ADD COLUMN IF NOT EXISTS is_admin BOOLEAN NOT NULL DEFAULT FALSE; -- Phase 4: Laravel session auth "remember me" token (idempotent). ALTER TABLE users ADD COLUMN IF NOT EXISTS remember_token TEXT; -- Phase 5-lite: client IP on scans for rate limiting (idempotent). ALTER TABLE scans ADD COLUMN IF NOT EXISTS ip TEXT; -- Phase 5.5: per-tag SMS metering (0 = unmetered/transitional). ALTER TABLE tags ADD COLUMN IF NOT EXISTS sms_allocated INTEGER NOT NULL DEFAULT 0; ALTER TABLE tags ADD COLUMN IF NOT EXISTS sms_used INTEGER NOT NULL DEFAULT 0; ALTER TABLE tags ADD COLUMN IF NOT EXISTS sms_period_start DATE NOT NULL DEFAULT CURRENT_DATE; -- Short URLs for SMS links (owner location pages). CREATE TABLE IF NOT EXISTS url_shortened ( code TEXT PRIMARY KEY, scan_id BIGINT NOT NULL REFERENCES scans(id), created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- Phase 5.5 quick wins: customer pause + order amounts (idempotent). ALTER TABLE users ADD COLUMN IF NOT EXISTS paused BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE orders ADD COLUMN IF NOT EXISTS amount NUMERIC(10,2) NOT NULL DEFAULT 0; -- Renewal tracking (lazy expiry): paid orders past renews_at are treated lapsed. ALTER TABLE orders ADD COLUMN IF NOT EXISTS renews_at TIMESTAMPTZ;