build(deps): Add migrate for neo4j data migrations

Implement a migration to merge duplicate user accounts with reactive
programming. Those duplicate user accounts existed, because around 40
users have decided to register again while we experienced a bug
related to normalized emails in our database.
This commit is contained in:
roschaefer 2020-01-14 01:47:03 +01:00
parent c0e2675912
commit 30268dec09
6 changed files with 146 additions and 10 deletions

View File

@ -0,0 +1,69 @@
import { throwError, of, concat } from 'rxjs'
import { tap, flatMap, mergeMap, map, catchError, filter } from 'rxjs/operators'
import CONFIG from '../src/config'
import { getNeode, getDriver } from '../src/bootstrap/neo4j'
import normalizeEmail from '../src/schema/resolvers//helpers/normalizeEmail'
export function up (next) {
const driver = getDriver()
const rxSession = driver.rxSession()
rxSession
.beginTransaction()
.pipe(
flatMap(txc =>
concat(
txc
.run("MATCH (email:EmailAddress) RETURN email {.email}")
.records()
.pipe(
map(record => {
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}), (oldUser)-[previousRelationship]-(oldEmail)
MATCH (user:User)-[:PRIMARY_EMAIL]->(email:EmailAddress {email: $normalizedEmail})
DELETE previousRelationship
WITH oldUser, oldEmail, user, email
CALL apoc.refactor.mergeNodes([user, oldUser], { properties: 'discard', mergeRels: true }) YIELD node as mergedUser
CALL apoc.refactor.mergeNodes([email, oldEmail], { properties: 'discard', mergeRels: true }) YIELD node as mergedEmail
RETURN user {.*}, email {.*}
`, { email, normalizedEmail })
.records()
.pipe(
map(r => ({
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 }) => console.log(`
Merged:
=============================
userId: ${user.id}
email: ${oldEmail} => ${email.email}
=============================
`),
complete: () => {
console.log('Merging of duplicate users completed')
next()
},
error: error => throw new Error(error)
})
}
export function down () {
throw new Error("Irreversible migration")
}

View File

@ -12,8 +12,9 @@
"test": "jest --forceExit --detectOpenHandles --runInBand",
"db:reset": "babel-node src/seed/reset-db.js",
"db:seed": "babel-node src/seed/seed-db.js",
"db:setup": "babel-node src/migration/setup.js",
"db:migrate": "babel-node src/migration/migrate.js"
"db:setup": "babel-node src/db/setup.js",
"db:migrate:create": "migrate create --template-file./src/db/migrationTemplate.js",
"db:migrate": "migrate --compiler 'js:@babel/register'"
},
"author": "Human Connection gGmbH",
"license": "MIT",
@ -80,6 +81,7 @@
"metascraper-url": "^5.10.3",
"metascraper-video": "^5.10.3",
"metascraper-youtube": "^5.10.5",
"migrate": "^1.6.2",
"minimatch": "^3.0.4",
"mustache": "^4.0.0",
"neo4j-driver": "^4.0.1",

View File

@ -0,0 +1,7 @@
export function up (next) {
next()
}
export function down (next) {
next()
}

View File

@ -1680,6 +1680,11 @@ ansi-regex@^4.0.0, ansi-regex@^4.1.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
ansi-styles@^3.1.0, ansi-styles@^3.2.0, ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@ -2492,6 +2497,17 @@ chalk@2.3.0:
escape-string-regexp "^1.0.5"
supports-color "^4.0.0"
chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
dependencies:
ansi-styles "^2.2.1"
escape-string-regexp "^1.0.2"
has-ansi "^2.0.0"
strip-ansi "^3.0.0"
supports-color "^2.0.0"
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@ -2704,6 +2720,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
commander@^2.9.0, commander@~2.20.3:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e"
@ -2714,11 +2735,6 @@ commander@^4.0.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-4.0.1.tgz#b67622721785993182e807f4883633e6401ba53c"
integrity sha512-IPF4ouhCP+qdlcmCedhxX4xiGBPyigb8v5NeUp+0LyhwLgxMqyp3S0vl7TAPfS/hiP7FC3caI/PB9lTmP8r1NA==
commander@~2.20.3:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@ -3001,6 +3017,11 @@ date-fns@2.9.0:
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.9.0.tgz#d0b175a5c37ed5f17b97e2272bbc1fa5aec677d2"
integrity sha512-khbFLu/MlzLjEzy9Gh8oY1hNt/Dvxw3J6Rbc28cVoYWQaC1S3YI4xwkF9ZWcjDLscbZlY9hISMr66RFzZagLsA==
dateformat@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062"
integrity sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=
dayjs@^1.8.19:
version "1.8.19"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.19.tgz#5117dc390d8f8e586d53891dbff3fa308f51abfe"
@ -3423,7 +3444,7 @@ escape-html@~1.0.3:
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
escape-string-regexp@^1.0.5:
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
@ -3834,7 +3855,6 @@ extsprintf@^1.2.0:
faker@Marak/faker.js#master:
version "4.1.0"
uid "3b2fa4aebccee52ae1bafc15d575061fb30c3cf1"
resolved "https://codeload.github.com/Marak/faker.js/tar.gz/3b2fa4aebccee52ae1bafc15d575061fb30c3cf1"
fast-deep-equal@^2.0.1:
@ -4387,6 +4407,13 @@ har-validator@~5.1.0:
ajv "^6.5.5"
har-schema "^2.0.0"
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=
dependencies:
ansi-regex "^2.0.0"
has-flag@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
@ -6119,6 +6146,20 @@ micromatch@^4.0.2:
braces "^3.0.1"
picomatch "^2.0.5"
migrate@^1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/migrate/-/migrate-1.6.2.tgz#8970d596780553fe9f545bdf83806df8473f025b"
integrity sha512-XAFab+ArPTo9BHzmihKjsZ5THKRryenA+lwob0R+ax0hLDs7YzJFJT5YZE3gtntZgzdgcuFLs82EJFB/Dssr+g==
dependencies:
chalk "^1.1.3"
commander "^2.9.0"
dateformat "^2.0.0"
dotenv "^4.0.0"
inherits "^2.0.3"
minimatch "^3.0.3"
mkdirp "^0.5.1"
slug "^0.9.2"
mime-db@1.40.0:
version "1.40.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
@ -6163,7 +6204,7 @@ mimic-response@^1.0.0, mimic-response@^1.0.1:
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
minimatch@^3.0.4:
minimatch@^3.0.3, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@ -7704,6 +7745,11 @@ serve-static@1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.17.1"
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
@ -7784,6 +7830,13 @@ slice-ansi@^2.1.0:
astral-regex "^1.0.0"
is-fullwidth-code-point "^2.0.0"
slug@^0.9.2:
version "0.9.4"
resolved "https://registry.yarnpkg.com/slug/-/slug-0.9.4.tgz#fad5f1ef33150830c7688cd8500514576eccabd8"
integrity sha512-3YHq0TeJ4+AIFbJm+4UWSQs5A1mmeWOTQqydW3OoPmQfNKxlO96NDRTIrp+TBkmvEsEFrd+Z/LXw8OD/6OlZ5g==
dependencies:
unicode ">= 0.3.1"
slug@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/slug/-/slug-2.1.0.tgz#293f8d53de7e55c15871846fd1bc36114841a8c7"
@ -8151,6 +8204,11 @@ supertest@~4.0.2:
methods "^1.1.2"
superagent "^3.8.3"
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
supports-color@^4.0.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b"