openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
239 lines (212 loc) • 7.5 kB
JavaScript
import logger from 'winston'
import pem from 'pem'
import { DbVersionModel } from './model/dbVersion'
import { KeystoreModel } from './model/keystore'
import { ClientModel } from './model/clients'
import { UserModel } from './model/users'
import { VisualizerModel } from './model/visualizer'
function dedupName (name, names, num) {
let newName
if (num) {
newName = `${name} ${num}`
} else {
newName = name
}
if (Array.from(names).includes(newName)) {
if (!num) {
num = 1
}
return dedupName(name, names, ++num)
} else {
return newName
}
}
// push new upgrade functions to this array, function must return a promise
// Warning: only add new function below existing functions, order matters!
const upgradeFuncs = []
upgradeFuncs.push({
description: 'Ensure that all certs have a fingerprint property',
func () {
return new Promise((resolve, reject) => {
KeystoreModel.findOne((err, keystore) => {
if (err) { return reject(err) }
if (!keystore) { return resolve() }
// convert server cert
pem.getFingerprint(keystore.cert.data, (err, obj) => {
if (err) { return reject(err) }
keystore.cert.fingerprint = obj.fingerprint
const promises = keystore.ca.map((cert) => {
return new Promise((resolve, reject) => {
pem.getFingerprint(cert.data, (err, obj) => {
if (err) { return reject(err) }
cert.fingerprint = obj.fingerprint
return resolve()
})
})
})
Promise.all(promises).then(() =>
keystore.save((err) => {
if (err != null) { logger.error(`Failed to save keystore: ${err}`) }
return resolve()
})
).catch(reject)
})
})
})
}
})
upgradeFuncs.push({
description: 'Convert clients link to certs via their domain to use the cert fingerprint instead',
func () {
return new Promise((resolve, reject) => {
ClientModel.find((err, clients) => {
if (err != null) {
logger.error(`Couldn't fetch all clients to upgrade db: ${err}`)
return reject(err)
}
KeystoreModel.findOne((err, keystore) => {
if (err != null) {
logger.error(`Couldn't fetch keystore to upgrade db: ${err}`)
return reject(err)
}
const promises = []
Array.from(clients).forEach((client) => {
if (keystore != null && keystore.ca != null) {
for (const cert of Array.from(keystore.ca)) {
if (client.clientDomain === cert.commonName && client.certFingerprint == null) {
client.certFingerprint = cert.fingerprint
break
}
}
promises.push(client.save())
}
})
Promise.all(promises).then(resolve).catch(reject)
})
})
})
}
})
// Adapt visualizer from an old version (core 2.0.0, console 1.6.0 and earlier)
//
// We follow the same migration strategy as console:
// https://github.com/jembi/openhim-console/blob/1047b49db2050bafa6b4797e3788fa716d1760b3/app/scripts/controllers/profile.js#L83-L109
function adaptOldVisualizerStructure (visualizer) {
visualizer.channels = []
visualizer.mediators = []
visualizer.time.minDisplayPeriod = 100
if (visualizer.endpoints) {
for (const endpoint of Array.from(visualizer.endpoints)) {
visualizer.channels.push({
eventType: 'channel',
eventName: endpoint.event.replace('channel-', ''),
display: endpoint.desc
})
}
delete visualizer.endpoints
}
if (visualizer.components) {
return (() => {
const result = []
for (const component of Array.from(visualizer.components)) {
const split = component.event.split('-')
if (split.length > 1) {
component.eventType = split[0]
component.eventName = split[1]
} else {
component.eventType = 'channel'
component.eventName = component.event
}
component.display = component.desc
delete component.event
result.push(delete component.desc)
}
return result
})()
}
}
upgradeFuncs.push({
description: 'Migrate visualizer setting from a user\'s profile to a shared collection',
func () {
return new Promise((resolve, reject) => {
UserModel.find((err, users) => {
if (err) {
return reject(err)
}
const visNames = []
const promises = []
users.forEach((user) => {
if ((user.settings != null ? user.settings.visualizer : undefined) != null) {
let vis = user.settings.visualizer
if (((vis.components != null ? vis.components.length : undefined) > 0) || ((vis.mediators != null ? vis.mediators.length : undefined) > 0) || ((vis.channels != null ? vis.channels.length : undefined) > 0) || ((vis.endpoints != null ? vis.endpoints.length : undefined) > 0)) {
const promise = new Promise((resolve, reject) => {
if (vis.endpoints) { // old version
adaptOldVisualizerStructure(vis)
}
let name = `${user.firstname} ${user.surname}'s visualizer`
name = dedupName(name, visNames)
vis.name = name
visNames.push(name)
vis = new VisualizerModel(vis)
logger.debug(`Migrating visualizer from user profile ${user.email}, using visualizer name '${name}'`)
vis.save((err, vis) => {
if (err) {
logger.error(`Error migrating visualizer from user profile ${user.email}: ${err.stack}`)
return reject(err)
}
// delete the visualizer settings from this user profile
user.set('settings.visualizer', null)
user.save((err, user) => {
if (err) { return reject(err) }
return resolve()
})
})
})
promises.push(promise)
}
}
})
Promise.all(promises).then(() => resolve()).catch(err => reject(err))
})
})
}
})
if (process.env.NODE_ENV === 'test') {
exports.upgradeFuncs = upgradeFuncs
exports.dedupName = dedupName
}
async function upgradeDbInternal () {
try {
const dbVer = (await DbVersionModel.findOne()) || new DbVersionModel({version: 0, lastUpdated: new Date()})
const upgradeFuncsToRun = upgradeFuncs.slice(dbVer.version)
for (const upgradeFunc of upgradeFuncsToRun) {
await upgradeFunc.func()
dbVer.version++
dbVer.lastUpdated = new Date()
await dbVer.save()
}
if (upgradeFuncsToRun.length === 0) {
logger.info('No database upgrades needed')
} else {
logger.info('Completed database upgrade')
}
} catch (err) {
logger.error(`There was an error upgrading your database, you will need to fix this manually to continue. ${err.stack}`)
}
}
export function upgradeDb (callback) {
return upgradeDbInternal()
.then((...values) => {
if (callback) {
callback(null, ...(values || []))
}
})
.catch(err => {
if (callback) {
callback(err)
}
})
}
if (!module.parent) {
exports.upgradeDb(() => process.exit())
}