Add script to fix duplicate contacts in database
This commit is contained in:
90
src/scripts/fix-duplicate-contacts.js
Normal file
90
src/scripts/fix-duplicate-contacts.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { db } from '../config/database.js';
|
||||||
|
import { contacts, emails } from '../db/schema.js';
|
||||||
|
import { eq, and, sql } from 'drizzle-orm';
|
||||||
|
import { logger } from '../utils/logger.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix duplicate contacts by merging them
|
||||||
|
* - Finds contacts with the same email address
|
||||||
|
* - Keeps the newest contact
|
||||||
|
* - Updates all emails to use the newest contact ID
|
||||||
|
* - Deletes old duplicate contacts
|
||||||
|
*/
|
||||||
|
async function fixDuplicateContacts() {
|
||||||
|
try {
|
||||||
|
logger.info('🔍 Finding duplicate contacts...');
|
||||||
|
|
||||||
|
// Find duplicate contacts (same userId + email)
|
||||||
|
const duplicates = await db
|
||||||
|
.select({
|
||||||
|
userId: contacts.userId,
|
||||||
|
email: contacts.email,
|
||||||
|
count: sql`count(*)::int`,
|
||||||
|
ids: sql`array_agg(${contacts.id} ORDER BY ${contacts.createdAt} DESC)`,
|
||||||
|
})
|
||||||
|
.from(contacts)
|
||||||
|
.groupBy(contacts.userId, contacts.email)
|
||||||
|
.having(sql`count(*) > 1`);
|
||||||
|
|
||||||
|
if (duplicates.length === 0) {
|
||||||
|
logger.success('✅ No duplicate contacts found!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Found ${duplicates.length} sets of duplicate contacts`);
|
||||||
|
|
||||||
|
let totalFixed = 0;
|
||||||
|
let totalDeleted = 0;
|
||||||
|
|
||||||
|
for (const dup of duplicates) {
|
||||||
|
const contactIds = dup.ids;
|
||||||
|
const newestContactId = contactIds[0]; // First one (ordered by createdAt DESC)
|
||||||
|
const oldContactIds = contactIds.slice(1); // Rest are duplicates
|
||||||
|
|
||||||
|
logger.info(`\n📧 Fixing duplicates for ${dup.email}:`);
|
||||||
|
logger.info(` - Keeping contact: ${newestContactId}`);
|
||||||
|
logger.info(` - Merging ${oldContactIds.length} duplicate(s): ${oldContactIds.join(', ')}`);
|
||||||
|
|
||||||
|
// Update all emails from old contacts to use the newest contact ID
|
||||||
|
for (const oldContactId of oldContactIds) {
|
||||||
|
const updateResult = await db
|
||||||
|
.update(emails)
|
||||||
|
.set({ contactId: newestContactId, updatedAt: new Date() })
|
||||||
|
.where(eq(emails.contactId, oldContactId))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (updateResult.length > 0) {
|
||||||
|
logger.success(` ✅ Updated ${updateResult.length} emails from ${oldContactId} → ${newestContactId}`);
|
||||||
|
totalFixed += updateResult.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the old duplicate contact
|
||||||
|
await db
|
||||||
|
.delete(contacts)
|
||||||
|
.where(eq(contacts.id, oldContactId));
|
||||||
|
|
||||||
|
logger.success(` 🗑️ Deleted duplicate contact: ${oldContactId}`);
|
||||||
|
totalDeleted++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.success(`\n✅ Cleanup complete!`);
|
||||||
|
logger.success(` - Fixed ${totalFixed} emails`);
|
||||||
|
logger.success(` - Deleted ${totalDeleted} duplicate contacts`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Error fixing duplicate contacts:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the script
|
||||||
|
fixDuplicateContacts()
|
||||||
|
.then(() => {
|
||||||
|
logger.success('🎉 Script completed successfully!');
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('💥 Script failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user