2020-04-22 17:24:15 +02:00
import mongoose from 'mongoose' ;
import cfenv from 'cfenv' ;
2020-06-05 08:50:06 +02:00
import _ from 'lodash' ;
import ChangelogModel from './models/changelog' ;
2020-04-22 17:24:15 +02:00
2020-05-04 15:48:07 +02:00
2020-04-22 17:24:15 +02:00
// database urls, prod db url is retrieved automatically
const TESTING_URL = 'mongodb://localhost/dfopdb_test' ;
const DEV_URL = 'mongodb://localhost/dfopdb' ;
2020-07-09 16:30:10 +02:00
const debugging = false ;
if ( process . env . NODE_ENV !== 'production' && debugging ) {
mongoose . set ( 'debug' , true ) ; // enable mongoose debug
}
2020-04-22 17:24:15 +02:00
export default class db {
private static state = { // db object and current mode (test, dev, prod)
db : null ,
mode : null ,
} ;
2020-05-18 14:47:22 +02:00
static connect ( mode = '' , done : Function = ( ) = > { } ) { // set mode to test for unit/integration tests, otherwise skip parameters. done is also only needed for testing
2020-04-22 17:24:15 +02:00
if ( this . state . db ) return done ( ) ; // db is already connected
// find right connection url
let connectionString : string = "" ;
if ( mode === 'test' ) { // testing
connectionString = TESTING_URL ;
this . state . mode = 'test' ;
}
else if ( process . env . NODE_ENV === 'production' ) {
let services = cfenv . getAppEnv ( ) . getServices ( ) ;
for ( let service in services ) {
if ( services [ service ] . tags . indexOf ( "mongodb" ) >= 0 ) {
connectionString = services [ service ] [ "credentials" ] . uri ;
}
}
this . state . mode = 'prod' ;
}
else {
connectionString = DEV_URL ;
this . state . mode = 'dev' ;
}
// connect to db
2020-05-06 14:39:04 +02:00
mongoose . connect ( connectionString , { useNewUrlParser : true , useUnifiedTopology : true , useCreateIndex : true , connectTimeoutMS : 10000 } , err = > {
2020-04-22 17:24:15 +02:00
if ( err ) done ( err ) ;
} ) ;
mongoose . connection . on ( 'error' , console . error . bind ( console , 'connection error:' ) ) ;
2020-07-10 09:42:05 +02:00
mongoose . connection . on ( 'connected' , ( ) = > { // evaluation connection behaviour on prod
if ( process . env . NODE_ENV !== 'test' ) { // Do not interfere with testing
console . info ( 'Database connected' ) ;
}
} ) ;
2020-04-23 13:59:45 +02:00
mongoose . connection . on ( 'disconnected' , ( ) = > { // reset state on disconnect
2020-05-28 11:47:51 +02:00
if ( process . env . NODE_ENV !== 'test' ) { // Do not interfere with testing
console . info ( 'Database disconnected' ) ;
2020-07-10 09:42:05 +02:00
// this.state.db = 0; // prod database connects and disconnects automatically
2020-05-28 11:47:51 +02:00
}
2020-04-23 13:59:45 +02:00
} ) ;
process . on ( 'SIGINT' , ( ) = > { // close connection when app is terminated
2020-05-28 11:47:51 +02:00
if ( ! this . state . db ) { // database still connected
mongoose . connection . close ( ( ) = > {
console . info ( 'Mongoose default connection disconnected through app termination' ) ;
process . exit ( 0 ) ;
} ) ;
}
2020-04-23 13:59:45 +02:00
} ) ;
2020-04-22 17:24:15 +02:00
mongoose . connection . once ( 'open' , ( ) = > {
2020-04-23 17:46:00 +02:00
mongoose . set ( 'useFindAndModify' , false ) ;
2020-05-07 21:55:29 +02:00
console . info ( process . env . NODE_ENV === 'test' ? '' : ` Connected to ${ connectionString } ` ) ;
2020-04-22 17:24:15 +02:00
this . state . db = mongoose . connection ;
done ( ) ;
} ) ;
}
2020-05-28 11:47:51 +02:00
static disconnect ( done ) {
mongoose . connection . close ( ( ) = > {
console . info ( process . env . NODE_ENV === 'test' ? '' : ` Disconnected from database ` ) ;
this . state . db = 0 ;
done ( ) ;
} ) ;
}
2020-04-22 17:24:15 +02:00
static getState ( ) {
return this . state ;
}
static drop ( done : Function = ( ) = > { } ) { // drop all collections of connected db (only dev and test for safety reasons ;)
if ( ! this . state . db || this . state . mode === 'prod' ) return done ( ) ; // no db connection or prod db
this . state . db . db . listCollections ( ) . toArray ( ( err , collections ) = > { // get list of all collections
if ( collections . length === 0 ) { // there are no collections to drop
return done ( ) ;
}
else {
let dropCounter = 0 ; // count number of dropped collections to know when to return done()
collections . forEach ( collection = > { // drop each collection
this . state . db . dropCollection ( collection . name , ( ) = > {
if ( ++ dropCounter >= collections . length ) { // all collections dropped
done ( ) ;
}
} ) ;
} ) ;
}
} ) ;
}
static loadJson ( json , done : Function = ( ) = > { } ) { // insert given JSON data into db, uses core mongodb methods
2020-05-18 14:47:22 +02:00
if ( ! this . state . db || ! json . hasOwnProperty ( 'collections' ) || json . collections . length === 0 ) { // no db connection or nothing to load
2020-04-22 17:24:15 +02:00
return done ( ) ;
2020-05-18 14:47:22 +02:00
}
2020-04-22 17:24:15 +02:00
let loadCounter = 0 ; // count number of loaded collections to know when to return done()
Object . keys ( json . collections ) . forEach ( collectionName = > { // create each collection
2020-05-07 21:55:29 +02:00
json . collections [ collectionName ] = this . oidResolve ( json . collections [ collectionName ] ) ;
2020-04-22 17:24:15 +02:00
this . state . db . createCollection ( collectionName , ( err , collection ) = > {
collection . insertMany ( json . collections [ collectionName ] , ( ) = > { // insert JSON data
if ( ++ loadCounter >= Object . keys ( json . collections ) . length ) { // all collections loaded
done ( ) ;
}
} ) ;
} ) ;
} ) ;
}
2020-05-07 21:55:29 +02:00
2020-06-05 08:50:06 +02:00
// changelog entry
static log ( req , thisOrCollection , conditions = null , data = null ) { // expects (req, this (from query helper)) or (req, collection, conditions, data)
if ( ! ( conditions || data ) ) { // (req, this)
data = thisOrCollection . _update ? _ . cloneDeep ( thisOrCollection . _update ) : { } ; // replace undefined with {}
Object . keys ( data ) . forEach ( key = > {
if ( key [ 0 ] === '$' ) {
data [ key . substr ( 1 ) ] = data [ key ] ;
delete data [ key ] ;
}
} ) ;
new ChangelogModel ( { action : req.method + ' ' + req . url , collectionName : thisOrCollection._collection.collectionName , conditions : thisOrCollection._conditions , data : data , user_id : req.authDetails.id ? req.authDetails.id : null } ) . save ( err = > {
if ( err ) console . error ( err ) ;
} ) ;
}
else { // (req, collection, conditions, data)
new ChangelogModel ( { action : req.method + ' ' + req . url , collectionName : thisOrCollection , conditions : conditions , data : data , user_id : req.authDetails.id ? req.authDetails.id : null } ) . save ( err = > {
if ( err ) console . error ( err ) ;
} ) ;
}
}
2020-05-07 21:55:29 +02:00
private static oidResolve ( object : any ) { // resolve $oid fields to actual ObjectIds recursively
Object . keys ( object ) . forEach ( key = > {
2020-05-18 14:47:22 +02:00
if ( object [ key ] !== null && object [ key ] . hasOwnProperty ( '$oid' ) ) { // found oid, replace
2020-05-07 21:55:29 +02:00
object [ key ] = mongoose . Types . ObjectId ( object [ key ] . $oid ) ;
}
2020-05-18 14:47:22 +02:00
else if ( typeof object [ key ] === 'object' && object [ key ] !== null ) { // deeper into recursion
2020-05-07 21:55:29 +02:00
object [ key ] = this . oidResolve ( object [ key ] ) ;
}
} ) ;
return object ;
}
2020-06-05 08:50:06 +02:00
} ;