Ocelot-Social/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts
2025-04-08 23:18:07 +02:00

86 lines
3.3 KiB
TypeScript

/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable promise/prefer-await-to-callbacks */
import { throwError, concat } from 'rxjs'
import { flatMap, mergeMap, map, catchError, filter } from 'rxjs/operators'
import { getDriver } from '@db/neo4j'
import normalizeEmail from '@schema/resolvers/helpers/normalizeEmail'
export const description = `
This migration merges duplicate :User and :EmailAddress nodes. It became
necessary after we implemented the email normalization but forgot to migrate
the existing data. Some (40) users decided to just register with a new account
but the same email address. On signup our backend would normalize the email,
which is good, but would also keep the existing unnormalized email address.
This led to about 40 duplicate user and email address nodes in our database.
`
export function up(next) {
const driver = getDriver()
const rxSession = driver.rxSession()
rxSession
.beginTransaction()
.pipe(
flatMap((txc: any) =>
concat(
txc
.run('MATCH (email:EmailAddress) RETURN email {.email}')
.records()
.pipe(
map((record: any) => {
const { email } = record.get('email')
const normalizedEmail = normalizeEmail(email)
return { email, normalizedEmail }
}),
filter(({ email, normalizedEmail }) => email !== normalizedEmail),
mergeMap(({ email, normalizedEmail }) => {
return txc
.run(
`
MATCH (oldUser:User)-[:PRIMARY_EMAIL]->(oldEmail:EmailAddress {email: $email})
MATCH (user:User)-[:PRIMARY_EMAIL]->(email:EmailAddress {email: $normalizedEmail})
WITH oldUser, oldEmail, user, email
CALL apoc.refactor.mergeNodes([user, oldUser], { properties: { createdAt: 'overwrite', \`.*\`: 'discard' }, mergeRels: true }) YIELD node as mergedUser
CALL apoc.refactor.mergeNodes([email, oldEmail], { properties: { createdAt: 'overwrite', verifiedAt: 'overwrite', \`.*\`: 'discard' }, mergeRels: true }) YIELD node as mergedEmail
RETURN user {.*}, email {.*}
`,
{ email, normalizedEmail },
)
.records()
.pipe(
map((r: any) => ({
oldEmail: email,
email: r.get('email'),
user: r.get('user'),
})),
)
}),
),
txc.commit(),
).pipe(catchError((err) => txc.rollback().pipe(throwError(err)))),
),
)
.subscribe({
next: ({ user, email, oldUser, oldEmail }) =>
// eslint-disable-next-line no-console
console.log(`
Merged:
=============================
userId: ${user.id}
email: ${oldEmail} => ${email.email}
=============================
`),
complete: () => {
// eslint-disable-next-line no-console
console.log('Merging of duplicate users completed')
next()
},
error: (error) => {
next(new Error(error), null)
},
})
}
export function down(next) {
next(new Error('Irreversible migration'))
}