'use strict' ; import { Config } from '@Base/config' ; import { MongoClient , Db , FilterQuery } from 'mongodb' ; import deepmerge from 'deepmerge' ; import { CriteriaFilter } from '@Entities/EntityFilters/CriteriaFilter' ; import { VKeyedCollection } from '@Tools/vTypes' ; import { SimpleObject , IsNullOrEmpty , IsNotNullOrEmpty } from '@Tools/Misc' ; import { Logger } from '@Tools/Logging' ; export let BaseClient ; export let Datab ; export async function setupDB ( ) { let connectUrl ; if ( IsNotNullOrEmpty ( Config . database [ "db-connection" ] ) ) { connectUrl = Config . database [ "db-connection" ] ; Logger . debug ( `setupDb: using user supplied connection string` ) ; } else { const userSpec = `${ Config . database [ "db-user" ] }:${ Config . database [ "db-pw" ] }` ; const hostSpec = `${ Config . database [ "db-host" ] }:${ Config . database [ "db-port" ] }` ; let optionsSpec = '' ; if ( Config . database [ "db-authdb" ] !== 'admin' ) { optionsSpec += `?authSource=${ Config . database [ "db-authdb" ] }` ; } ; connectUrl = `mongodb://${ userSpec }@${ hostSpec }/${ optionsSpec }` ; Logger . debug ( `setupDb: connecting to DB at: mongodb://USER@${ hostSpec }/${ optionsSpec }` ) ; } ; BaseClient = await MongoClient . connect ( connectUrl , { useUnifiedTopology : true , useNewUrlParser : true } ) ; Datab = BaseClient . db ( Config . database . db ) ; await DoDatabaseFormatChanges ( ) ; await BuildIndexes ( ) ; return ; } ; export async function createObject ( pCollection , pObject ) { return Datab . collection ( pCollection ) . insertOne ( pObject , { forceServerObjectId : true } ) ; } ; export async function getObject ( pCollection , pCriteria , pCollation ? ) { if ( pCollation ) { Logger . cdebug ( 'db-query-detail' , `Db.getObject: collection=${ pCollection }, criteria=${ JSON . stringify ( pCriteria . criteriaParameters ( ) ) }` ) ; const cursor = Datab . collection ( pCollection ) . find ( pCriteria . criteriaParameters ( ) ) . collation ( pCollation ) ; if ( await cursor . hasNext ( ) ) { return cursor . next ( ) ; } ; return null ; } ; Logger . cdebug ( 'db-query-detail' , `Db.getObject: collection=${ pCollection }, criteria=${ JSON . stringify ( pCriteria . criteriaParameters ( ) ) }` ) ; return Datab . collection ( pCollection ) . findOne ( pCriteria . criteriaParameters ( ) ) ; } ; export async function updateObjectFields ( pCollection , pCriteria , pFields ) { let doSet = false ; let doUnset = false ; const set = { } ; const unset = { } ; const op = { } ; if ( IsNullOrEmpty ( pFields ) || Object . keys ( pFields ) . length === 0 ) { return ; } ; for ( const [ key , value ] of Object . entries ( pFields ) ) { if ( key === 'id' ) continue ; if ( value === null ) { doUnset = true ; unset [ key ] = '' ; } else { doSet = true ; set [ key ] = value ; } ; } ; if ( doSet ) { op . $set = set ; } ; if ( doUnset ) { op . $unset = unset ; } ; Logger . cdebug ( 'db-query-detail' , `Db.updateObjectFields: collection=${ pCollection }, criteria=${ JSON . stringify ( pCriteria . criteriaParameters ( ) ) }, op=${ JSON . stringify ( op ) }` ) ; return Datab . collection ( pCollection ) . findOneAndUpdate ( pCriteria . criteriaParameters ( ) , op , { returnOriginal : false } ) ; } ; export async function deleteMany ( pCollection , pCriteria ) { const result = await Datab . collection ( pCollection ) . deleteMany ( pCriteria . criteriaParameters ( ) ) ; return result . deletedCount ?? 0 ; } ; export async function deleteOne ( pCollection , pCriteria ) { let ret = false ; const result = await Datab . collection ( pCollection ) . deleteOne ( pCriteria . criteriaParameters ( ) ) ; if ( result . result && result . result . ok ) { ret = result . result . ok === 1 ; } else { ret = ( result . deletedCount ?? 0 ) > 0 ; } ; return ret ; } ; export async function countObjects ( pCollection , pFilter ) { Logger . cdebug ( 'db-query-detail' , `Db.countObjects: collection=${ pCollection }, criteria=${ JSON . stringify ( pFilter . criteriaParameters ( ) ) }` ) ; return Datab . collection ( pCollection ) . countDocuments ( pFilter . criteriaParameters ( ) as FilterQuery < any > ) ; } ; export async function * getObjects ( pCollection , pPager ? , pInfoer ? , pScoper ? ) { let criteria = { } ; let sortCriteria ; if ( pPager ) { criteria = deepmerge ( criteria , pPager . criteriaParameters ( ) ) ; if ( pPager . sortCriteriaParameters ( ) ) { sortCriteria = deepmerge ( sortCriteria ?? { } , pPager . sortCriteriaParameters ( ) ) ; } ; } ; if ( pInfoer ) { criteria = deepmerge ( criteria , pInfoer . criteriaParameters ( ) ) ; if ( pInfoer . sortCriteriaParameters ( ) ) { sortCriteria = deepmerge ( sortCriteria ?? { } , pInfoer . sortCriteriaParameters ( ) ) ; } ; } ; if ( pScoper ) { criteria = deepmerge ( criteria , pScoper . criteriaParameters ( ) ) ; if ( pScoper . sortCriteriaParameters ( ) ) { sortCriteria = deepmerge ( sortCriteria ?? { } , pScoper . sortCriteriaParameters ( ) ) ; } ; } ; Logger . cdebug ( 'db-query-detail' , `Db.getObjects: collection=${ pCollection }, criteria=${ JSON . stringify ( criteria ) }` ) ; const cursor = Datab . collection ( pCollection ) . find ( criteria ) ; if ( sortCriteria ) { Logger . cdebug ( 'db-query-detail' , `Db.getObjects: collection=${ pCollection }, sortCriteria=${ JSON . stringify ( sortCriteria ) }` ) ; cursor . sort ( sortCriteria ) ; } ; while ( await cursor . hasNext ( ) ) { const nextItem = await cursor . next ( ) ; if ( ( pPager ? pPager . criteriaTest ( nextItem ) : true ) && ( pInfoer ? pInfoer . criteriaTest ( nextItem ) : true ) && ( pScoper ? pScoper . criteriaTest ( nextItem ) : true ) ) { yield nextItem ; } ; } ; } ; const domainCollection = 'domains' ; const accountCollection = 'accounts' ; const placeCollection = 'places' ; const tokenCollection = 'tokens' ; export let noCaseCollation = { locale : 'en_US' , strength : 2 } ; async function BuildIndexes ( ) { await Datab . createIndex ( accountCollection , { 'id' : 1 } ) ; await Datab . createIndex ( accountCollection , { 'username' : 1 } , { collation : noCaseCollation } ) ; await Datab . createIndex ( accountCollection , { 'locationNodeId' : 1 } ) ; await Datab . createIndex ( accountCollection , { 'email' : 1 } , { collation : noCaseCollation } ) ; await Datab . createIndex ( domainCollection , { 'id' : 1 } ) ; await Datab . createIndex ( domainCollection , { 'apiKey' : 1 } ) ; await Datab . createIndex ( domainCollection , { 'lastSenderKey' : 1 } ) ; await Datab . createIndex ( placeCollection , { 'id' : 1 } ) ; await Datab . createIndex ( placeCollection , { 'name' : 1 } , { collation : { locale : 'en_US' , strength : 2 } } ) ; } ; async function DoDatabaseFormatChanges ( ) { await RenameDbField ( domainCollection , 'domainId' , 'id' ) ; await RenameDbField ( domainCollection , 'placeName' , 'name' ) ; await RenameDbField ( domainCollection , 'sponserAccountId' , 'sponsorAccountId' ) ; await RenameDbField ( domainCollection , 'whenDomainEntryCreated' , 'whenCreated' ) ; await RenameDbField ( accountCollection , 'accountId' , 'id' ) ; await RenameDbField ( accountCollection , 'whenAccountCreated' , 'whenCreated' ) ; await RenameDbField ( placeCollection , 'placeId' , 'id' ) ; await RenameDbField ( placeCollection , 'address' , 'path' ) ; await RenameDbField ( placeCollection , 'whenPlaceEntryCreated' , 'whenCreated' ) ; await RenameDbField ( tokenCollection , 'tokenId' , 'id' ) ; await RenameDbField ( tokenCollection , 'tokenExpirationTime' , 'expirationTime' ) ; await RenameDbField ( tokenCollection , 'tokenCreationTime' , 'whenCreated' ) ; } ; async function RenameDbField ( pCollection , pFrom , pTo ) { let updateCount = 0 ; await Datab . collection ( pCollection ) . find ( SimpleObject ( pFrom , { '$exists' : true } ) ) . forEach ( doc => { updateCount ++ ; Datab . collection ( pCollection ) . updateOne ( { _id : doc . _id } , { '$rename' : SimpleObject ( pFrom , pTo ) } ) ; } ) ; if ( updateCount > 0 ) { Logger . debug ( `Db.DoDatabaseFormatChanges: ${ updateCount } ${ pCollection }.${ pFrom } renames` ) ; } ; } ;