-- Additive migration: old releases can still read their original email column. -- Runtime identity is always account(provider_id, account_id), never an email pattern. ALTER TABLE "user" ADD COLUMN IF NOT EXISTS contact_email text; ALTER TABLE "user" ADD COLUMN IF NOT EXISTS root_admin boolean NOT NULL DEFAULT false; ALTER TABLE "user" ADD COLUMN IF NOT EXISTS merged_into_user_id text; ALTER TABLE account ADD COLUMN IF NOT EXISTS profile jsonb; UPDATE "user" SET contact_email = lower(email) WHERE contact_email IS NULL AND email !~* '@(osu\.local|discord\.local|identity\.invalid)$'; CREATE UNIQUE INDEX IF NOT EXISTS user_contact_email_unique ON "user" (contact_email); -- Fail the transaction for conflicting connections; never guess which user owns one. CREATE UNIQUE INDEX IF NOT EXISTS account_provider_identity_idx ON account (provider_id, account_id); CREATE UNIQUE INDEX IF NOT EXISTS account_user_provider_idx ON account (user_id, provider_id); -- Profile backfill is best effort; OAuth refreshes it on the next successful sign-in. UPDATE account a SET profile = jsonb_build_object( 'username', u.name, 'image', u.image, 'email', null, 'emailVerified', false, 'countryCode', u.country_code, 'globalRank', r.osu_rank_at_seed ) FROM "user" u LEFT JOIN player_rating r ON r.user_id = u.id WHERE a.user_id = u.id AND a.provider_id IN ('osu', 'discord') AND a.profile IS NULL; -- Repair solo roster snapshots only where ownership is unambiguous. Match discovery -- and authorization can then use the same persisted participant roster. INSERT INTO team_member (team_id, user_id, role) SELECT t.id, t.owner_id, 'owner' FROM team t JOIN "user" u ON u.id = t.owner_id WHERE t.is_personal AND NOT EXISTS ( SELECT 1 FROM team_member tm WHERE tm.team_id = t.id AND tm.user_id = t.owner_id ); INSERT INTO match_participant_player (participant_id, user_id) SELECT p.id, t.owner_id FROM match_participant p JOIN team t ON t.id = p.team_id JOIN "user" u ON u.id = t.owner_id WHERE t.is_personal AND NOT EXISTS ( SELECT 1 FROM match_participant_player pp WHERE pp.participant_id = p.id );