diff --git a/backend/package.json b/backend/package.json index 4d23406c7..bbdf951ad 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,8 +13,10 @@ "db:reset": "babel-node src/seed/reset-db.js", "db:seed": "babel-node src/seed/seed-db.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'" + "__migrate": "migrate --compiler 'js:@babel/register' --migrations-dir ./src/db/migrations", + "db:migrate": "yarn run __migrate --store ./src/db/migrate/store.js", + "db:migrate:create": "yarn run __migrate --template-file ./src/db/migrate/template.js create", + "production:db:migrate": "migrate --migrations-dir ./dist/db/migrations --store ./dist/db/migrate/store.js" }, "author": "Human Connection gGmbH", "license": "MIT", @@ -106,7 +108,7 @@ "@babel/node": "~7.8.3", "@babel/plugin-proposal-throw-expressions": "^7.8.3", "@babel/preset-env": "~7.8.3", - "@babel/register": "~7.8.3", + "@babel/register": "^7.8.3", "apollo-server-testing": "~2.9.16", "babel-core": "~7.0.0-0", "babel-eslint": "~10.0.3", diff --git a/backend/src/db/migrate/store.js b/backend/src/db/migrate/store.js new file mode 100644 index 000000000..68bf4e8d9 --- /dev/null +++ b/backend/src/db/migrate/store.js @@ -0,0 +1,57 @@ +import { getDriver } from '../../bootstrap/neo4j' + +class Store { + async load(fn) { + const driver = getDriver() + const session = driver.session() + const readTxResultPromise = session.readTransaction(async txc => { + const result = await txc.run( + 'MATCH (migration:Migration) RETURN migration {.*} ORDER BY migration.timestamp DESC', + ) + return result.records.map(r => r.get('migration')) + }) + try { + const migrations = await readTxResultPromise + if (migrations.length <= 0) { + // eslint-disable-next-line no-console + console.log( + "No migrations found in database. If it's the first time you run migrations, then this is normal.", + ) + return fn(null, {}) + } + const [{ title: lastRun }] = migrations + fn(null, { lastRun, migrations }) + } catch (error) { + console.log(error) // eslint-disable-line no-console + } finally { + session.close() + } + } + + async save(set, fn) { + const driver = getDriver() + const session = driver.session() + const { migrations } = set + const writeTxResultPromise = session.writeTransaction(txc => { + return Promise.all( + migrations.map(migration => { + const { title, description, timestamp } = migration + const properties = { title, description, timestamp } + return txc.run('CREATE (migration:Migration) SET migration += $properties', { + properties, + }) + }), + ) + }) + try { + await writeTxResultPromise + } catch (error) { + console.log(error) // eslint-disable-line no-console + } finally { + session.close() + fn() + } + } +} + +module.exports = Store diff --git a/backend/src/db/migrate/template.js b/backend/src/db/migrate/template.js new file mode 100644 index 000000000..941f2a9e3 --- /dev/null +++ b/backend/src/db/migrate/template.js @@ -0,0 +1,7 @@ +export function up(next) { + next() +} + +export function down(next) { + next() +} diff --git a/backend/src/db/migrationTemplate.js b/backend/src/db/migrationTemplate.js deleted file mode 100644 index dff6564ef..000000000 --- a/backend/src/db/migrationTemplate.js +++ /dev/null @@ -1,7 +0,0 @@ -export function up (next) { - next() -} - -export function down (next) { - next() -} diff --git a/backend/migrations/1579387929122-foo.js b/backend/src/db/migrations/1579387929122-merge_duplicate_user_accounts.js similarity index 77% rename from backend/migrations/1579387929122-foo.js rename to backend/src/db/migrations/1579387929122-merge_duplicate_user_accounts.js index 9d23051ac..7d2abcdeb 100644 --- a/backend/migrations/1579387929122-foo.js +++ b/backend/src/db/migrations/1579387929122-merge_duplicate_user_accounts.js @@ -1,10 +1,18 @@ 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' +import CONFIG from '../../src/config' +import { getNeode, getDriver } from '../../src/bootstrap/neo4j' +import normalizeEmail from '../../src/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() diff --git a/backend/src/db/setup.sj b/backend/src/db/setup.js similarity index 100% rename from backend/src/db/setup.sj rename to backend/src/db/setup.js diff --git a/backend/src/models/Post.js b/backend/src/models/Post.js index c29036009..e2e153a1b 100644 --- a/backend/src/models/Post.js +++ b/backend/src/models/Post.js @@ -11,7 +11,7 @@ export default { direction: 'in', }, title: { type: 'string', disallow: [null], min: 3 }, - slug: { type: 'string', allow: [null], unique: 'true', }, + slug: { type: 'string', allow: [null], unique: 'true' }, content: { type: 'string', disallow: [null], min: 3 }, contentExcerpt: { type: 'string', allow: [null] }, image: { type: 'string', allow: [null] }, diff --git a/backend/yarn.lock b/backend/yarn.lock index db1062af0..2c1b2efee 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -751,7 +751,7 @@ levenary "^1.1.0" semver "^5.5.0" -"@babel/register@^7.8.3", "@babel/register@~7.8.3": +"@babel/register@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.8.3.tgz#5d5d30cfcc918437535d724b8ac1e4a60c5db1f8" integrity sha512-t7UqebaWwo9nXWClIPLPloa5pN33A2leVs8Hf0e9g9YwUP8/H9NeR7DJU+4CXo23QtjChQv5a3DjEtT83ih1rg==