-- Account identifiers, verified addresses, and one-time mail tokens. -- Existing primary addresses are grandfathered as verified once. ALTER TABLE profiles ADD COLUMN IF NOT EXISTS username TEXT; ALTER TABLE profiles ADD COLUMN IF NOT EXISTS secondary_email TEXT; ALTER TABLE profiles ADD COLUMN IF NOT EXISTS email_verified_at TEXT; ALTER TABLE profiles ADD COLUMN IF NOT EXISTS secondary_email_verified_at TEXT; ALTER TABLE profiles ADD COLUMN IF NOT EXISTS pending_email TEXT; ALTER TABLE profiles ADD COLUMN IF NOT EXISTS pending_secondary_email TEXT; UPDATE profiles SET email_verified_at = COALESCE(NULLIF(created, ''), CURRENT_TIMESTAMP::text) WHERE email_verified_at IS NULL OR email_verified_at = ''; CREATE TABLE IF NOT EXISTS password_reset_tokens ( id TEXT PRIMARY KEY, profile_id TEXT NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, expires_at TEXT NOT NULL, used_at TEXT, created TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP::text ); CREATE TABLE IF NOT EXISTS email_verification_tokens ( id TEXT PRIMARY KEY, profile_id TEXT NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, purpose TEXT NOT NULL CHECK (purpose IN ('primary', 'secondary')), email TEXT NOT NULL, token_hash TEXT NOT NULL UNIQUE, expires_at TEXT NOT NULL, used_at TEXT, created TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP::text ); CREATE UNIQUE INDEX IF NOT EXISTS profiles_username_lower_idx ON profiles (lower(username)) WHERE username IS NOT NULL AND username <> ''; CREATE UNIQUE INDEX IF NOT EXISTS profiles_secondary_email_lower_idx ON profiles (lower(secondary_email)) WHERE secondary_email IS NOT NULL AND secondary_email <> ''; CREATE UNIQUE INDEX IF NOT EXISTS profiles_pending_email_lower_idx ON profiles (lower(pending_email)) WHERE pending_email IS NOT NULL AND pending_email <> ''; CREATE UNIQUE INDEX IF NOT EXISTS profiles_pending_secondary_email_lower_idx ON profiles (lower(pending_secondary_email)) WHERE pending_secondary_email IS NOT NULL AND pending_secondary_email <> '';