CREATE TABLE "user_email_identities" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "user_id" uuid NOT NULL, "email" varchar(255) NOT NULL, "verified_at" timestamp with time zone, "source" varchar(30) NOT NULL, "created_at" timestamp with time zone DEFAULT now() NOT NULL, "last_used_at" timestamp with time zone ); --> statement-breakpoint ALTER TABLE "user_email_identities" ADD CONSTRAINT "user_email_identities_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint CREATE INDEX "user_email_identities_user_id_idx" ON "user_email_identities" USING btree ("user_id");--> statement-breakpoint CREATE INDEX "user_email_identities_email_idx" ON "user_email_identities" USING btree ("email");--> statement-breakpoint CREATE UNIQUE INDEX "user_email_identities_user_id_email_unique" ON "user_email_identities" USING btree ("user_id","email");--> statement-breakpoint CREATE UNIQUE INDEX "user_email_identities_verified_email_unique" ON "user_email_identities" USING btree ("email") WHERE "user_email_identities"."verified_at" IS NOT NULL;--> statement-breakpoint -- Backfill: every existing user gets a `source='primary'` identity with -- their `users.email` (lowercased). We trust existing rows because they -- came in via our own sign-up/credentials flow, so `verified_at` is set -- to `created_at`. This is what makes the new -- `ensureUserIdByVerifiedEmail` lookup return the same `users.id` that -- `ensureUserIdByEmail` used to return for every pre-existing user. INSERT INTO "user_email_identities" ("user_id", "email", "verified_at", "source", "created_at") SELECT "id", lower("email"), "created_at", 'primary', "created_at" FROM "users" ON CONFLICT ("user_id", "email") DO NOTHING;