🐛 fix: user email unique migration error (#10548)

This commit is contained in:
YuTengjing
2025-12-02 12:37:42 +08:00
committed by GitHub
parent 16e6c4dcaa
commit ca2a1a21f6
5 changed files with 59 additions and 7 deletions
@@ -35,6 +35,8 @@ CREATE INDEX IF NOT EXISTS "verification_identifier_idx" ON "verifications" USIN
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_email_unique') THEN
-- Normalize empty emails so the unique constraint can be created safely
UPDATE "users" SET "email" = NULL WHERE "email" = '';
ALTER TABLE "users" ADD CONSTRAINT "users_email_unique" UNIQUE ("email");
END IF;
END $$;
+1 -1
View File
@@ -884,7 +884,7 @@
"\nCREATE INDEX IF NOT EXISTS \"account_userId_idx\" ON \"accounts\" USING btree (\"user_id\");\n",
"\nCREATE INDEX IF NOT EXISTS \"auth_session_userId_idx\" ON \"auth_sessions\" USING btree (\"user_id\");\n",
"\nCREATE INDEX IF NOT EXISTS \"verification_identifier_idx\" ON \"verifications\" USING btree (\"identifier\");\n",
"\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_email_unique') THEN\n ALTER TABLE \"users\" ADD CONSTRAINT \"users_email_unique\" UNIQUE (\"email\");\n END IF;\nEND $$;\n",
"\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_email_unique') THEN\n UPDATE \"users\" SET \"email\" = NULL WHERE \"email\" = '';\n ALTER TABLE \"users\" ADD CONSTRAINT \"users_email_unique\" UNIQUE (\"email\");\n END IF;\nEND $$;\n",
"\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_phone_number_unique') THEN\n ALTER TABLE \"users\" ADD CONSTRAINT \"users_phone_number_unique\" UNIQUE (\"phone_number\");\n END IF;\nEND $$;\n"
],
"bps": true,
+25 -5
View File
@@ -149,9 +149,11 @@ export class UserModel {
};
updateUser = async (value: Partial<UserItem>) => {
const nextValue = UserModel.normalizeUniqueUserFields(value);
return this.db
.update(users)
.set({ ...value, updatedAt: new Date() })
.set({ ...nextValue, updatedAt: new Date() })
.where(eq(users.id, this.userId));
};
@@ -193,6 +195,26 @@ export class UserModel {
.where(eq(users.id, this.userId));
};
/**
* Normalize unique user fields so empty strings become null, keeping unique constraints safe.
*/
private static normalizeUniqueUserFields = <
T extends { email?: string | null; phone?: string | null },
>(
value: T,
) => {
const normalizedEmail =
typeof value.email === 'string' && value.email.trim() === '' ? null : value.email;
const normalizedPhone =
typeof value.phone === 'string' && value.phone.trim() === '' ? null : value.phone;
return {
...value,
...(value.email !== undefined ? { email: normalizedEmail } : {}),
...(value.phone !== undefined ? { phone: normalizedPhone } : {}),
};
};
// Static method
static makeSureUserExist = async (db: LobeChatDatabase, userId: string) => {
await db.insert(users).values({ id: userId }).onConflictDoNothing();
@@ -205,10 +227,8 @@ export class UserModel {
if (!!user) return { duplicate: true };
}
const [user] = await db
.insert(users)
.values({ ...params })
.returning();
const normalizedParams = this.normalizeUniqueUserFields(params);
const [user] = await db.insert(users).values(normalizedParams).returning();
return { duplicate: false, user };
};
+26
View File
@@ -24,7 +24,33 @@ DATABASE_DRIVER=node
if you have any other question, please open issue here: https://github.com/lobehub/lobe-chat/issues
`;
const DUPLICATE_EMAIL_HINT = `------------------------------------------------------------------------------------------
⚠️ Database migration failed due to duplicate email addresses in the users table.
The database schema requires each email to be unique, but multiple users currently share the same email value.
Recommended solutions (choose one and rerun the migration):
1) Update duplicate emails to make them unique: change the conflicting email addresses to another unique email address or just change them email to NULL
2) Remove duplicate user records (dangerously, only if safe to delete)
⚠️ IMPORTANT: Always backup your database before making any changes!
To find duplicate emails, run this query:
\`\`\`sql
SELECT email, COUNT(*) as count
FROM users
WHERE email IS NOT NULL
GROUP BY email
HAVING COUNT(*) > 1;
\`\`\`
If you need further assistance, please open an issue: https://github.com/lobehub/lobe-chat/issues
`;
module.exports = {
DB_FAIL_INIT_HINT,
DUPLICATE_EMAIL_HINT,
PGVECTOR_HINT,
};
+5 -1
View File
@@ -4,7 +4,7 @@ import { migrate as nodeMigrate } from 'drizzle-orm/node-postgres/migrator';
import { join } from 'node:path';
// @ts-ignore tsgo handle esm import cjs and compatibility issues
import { DB_FAIL_INIT_HINT, PGVECTOR_HINT } from './errorHint';
import { DB_FAIL_INIT_HINT, DUPLICATE_EMAIL_HINT, PGVECTOR_HINT } from './errorHint';
// Read the `.env` file if it exists, or a file specified by the
// dotenv_config_path parameter that's passed to Node.js
@@ -39,8 +39,12 @@ if (!isDesktop && connectionString) {
const errMsg = err.message as string;
const constraint = (err as { constraint?: string })?.constraint;
if (errMsg.includes('extension "vector" is not available')) {
console.info(PGVECTOR_HINT);
} else if (constraint === 'users_email_unique' || errMsg.includes('users_email_unique')) {
console.info(DUPLICATE_EMAIL_HINT);
} else if (errMsg.includes(`Cannot read properties of undefined (reading 'migrate')`)) {
console.info(DB_FAIL_INIT_HINT);
}