Repository navigation
Enforce normalised user emails with a check constraint - #1164
Merged
Merged
Conversation
Every stored address is lower-cased and trimmed since the email case fix and the manual merge of the case-variant duplicates. The check keeps it that way: with the existing unique index on email, an address can only exist once in any case, and a write path that forgets to normalise fails instead of creating a second account.
A failing migration keeps the app from starting. Self-hosted installs can still hold case-variant duplicates, so the constraint is only added when every row satisfies it, and a notice explains how to add it later.
helpcodeai
enabled auto-merge (squash)
October 19, 2026 19:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
#1159 made every write path normalise addresses (trimmed, lower case) and every lookup case-insensitive, and its migration lower-cased the existing rows. The rows that only differed in case were merged by hand afterwards, so production now has no address that is not normalised and no case-variant duplicates.
What
A migration that adds
users_email_normalised_check:CHECK (email = lower(btrim(email))).email, an address can only exist once, in any case.A check constraint was chosen rather than a unique index on
lower(email): Prisma cannot model expression indexes and would try to drop one on the nextmigrate dev, while it leaves check constraints alone.Safety
packages/backend/scripts/report-duplicate-emails.sql, then run theALTER TABLEfrom the migration by hand.