# Initial migration from an existing database

<!-- TOC depthFrom:2 -->
- [1. Extract the state of the database](#1-extract-the-state-of-the-database)
- [2. Commit the state](#2-commit-the-state)
- [3. Create the `0.initial-migration.js` initial migration script](#3-create-the-0initial-migrationjs-initial-migration-script)
<!-- /TOC -->

## 1. Extract the state of the database
With `readonly` credentials, you can execute the following script to save a
snapshot of the current database state:
```js
const fs = require('fs');
const logger = require('chpr-logger');
const MongoDbConnector = require('chpr-mongodb');

const models = require('./src/models/definitions');
const { migrate: config } = require('./src/config');
/**
 * Extract the state of a database and save it to the file `states.json`
 */
async function main() {
  logger.info('Initial structure extraction');
  const mongodb = new MongoDbConnector(config.mongodb);
  try {
    logger.info('MongoDb connection');
    await mongodb.connect();
    const states = await mongodb.states();
    console.log(require('util').inspect(states, { showHidden: false, depth: null }));
    fs.writeFileSync('states.json', JSON.stringify(states, null, '  '));
  } finally {
    await mongodb.disconnect();
  }
}
main()
  .then(() => process.exit(0))
  .catch(err => {
    logger.error(err);
    process.exit(1);
  });
```
## 2. Commit the state
Move the `states.json` into `src/migrations/` with a name including the
date of the snapshot.

*Example: 0.2018-01-10-api-node-database-state.json*

## 3. Create the `0.initial-migration.js` initial migration script

This script will allow to initiliaze the state from the current state in production.
Collections to be initialized need to be selected when performing the initial migration ( users, rides and ride bookings in the example below ).

```js
'use strict';

const fs = require('fs');
const path = require('path');
const _ = require('lodash');

const INITIAL_STATE_FILE = path.resolve(__dirname, '0.2018-01-10-api-node-database-state.json');

/**
 * Restore a collection from its state
 *
 * @param {MongoConnection} db
 * @param {object} state
 * @param {string} collectionName
 * @returns {void} void
 */
async function restoreCollection(db, state, collectionName) {
  const collectionPosition = _.findIndex(state.collections, { name: collectionName });
  const collection = state.collections[collectionPosition];
  const collectionIndexes = state.collectionIndexes[collectionPosition];

  await db.createCollection(collectionName, collection.options);
  await db.command({ collMod: collectionName, validator: {} });

  // eslint-disable-next-line no-await-in-loop
  for (const index of collectionIndexes) {
    const options = _.omit(index, 'v', 'key', 'ns', 'background');

    await db.collection(collectionName).ensureIndex(index.key, Object.assign(options, {
      background: true
    }));
  }
}

/**
 * Downgrade the state to previous version
 * @param {object} mongodb
 * @return {Promise<boolean>} Status of the migration
 */
async function rollback(mongodb) {
  await Promise.all(Array.from(mongodb.DATABASES.keys())
    .map(databaseName => mongodb.db(databaseName).dropDatabase()));

  return true;
}

/**
 * Upgrade the state to version x.x.x
 * @param {object} mongodb
 * @return {Promise<boolean>} Status of the migration
 */
async function update(mongodb) {
  const db = mongodb.db('api-node');

  const state = _.find(JSON.parse(fs.readFileSync(INITIAL_STATE_FILE)), { name: 'api-node' });

  await Promise.all(['users', 'rides', 'ridebookings']
    .map(name => restoreCollection(db, state, name)));

  return true;
}

module.exports = {
  rollback,
  update
};
```

## Real-life example

- [corpo-reporting](https://github.com/transcovo/corpo-reporting)
- [kenny](https://github.com/transcovo/kenny)
