Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mCSD Changes plus more #122

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7f384c4
Initial support for mCSD.
lukeaduncan Jan 15, 2018
79719e0
Finished up adding in required queries for mCSD. Removed conformance…
lukeaduncan Jan 16, 2018
8d94675
Added in function to work as an mCSD update consumer when passed a UR…
lukeaduncan Jan 16, 2018
e2fdfde
Removed debugging statement.
lukeaduncan Jan 16, 2018
40abee8
Attempted change to get chained parameters to reference self.
lukeaduncan Jan 16, 2018
d30861f
Fixed self referential chained parameters with assistance from Ryan C…
lukeaduncan Jan 16, 2018
cf47157
Added in basic ATNA support for mCSD queries (for Practitioner so far…
lukeaduncan Jan 17, 2018
e02244d
Added in communication and communicationrequest resources.
lukeaduncan Jan 17, 2018
c780475
Added in simple echo plugin for mACM for communication request.
lukeaduncan Jan 17, 2018
57c94af
Fixed some content type handling as well as with _format.
lukeaduncan Jan 17, 2018
0b77116
Fixed typo in conversion method.
lukeaduncan Jan 17, 2018
bceb357
Fixed username in atna for mCSD and fixed typo in convert plugin.
lukeaduncan Jan 17, 2018
bad33b1
Updated ATNA message to be compliant for mCSD. Fixed up an issue wit…
lukeaduncan Jan 18, 2018
9f6ec56
Fixed history query to use the correct resource type.
lukeaduncan Jan 31, 2018
fd18fe1
Adding mCSD to CSD translator as a history
ashaban Apr 9, 2018
e7917ad
removing unused module
ashaban Apr 9, 2018
4aec672
Minor changes to variable name
ashaban Apr 9, 2018
01b9be6
Merge pull request #1 from ashaban/master
lukeaduncan May 11, 2018
f2f7caa
Fixed typo in location partOf query.
lukeaduncan Jun 11, 2018
a1b0372
Updated server to allow multiple databases by URL which is saved in t…
lukeaduncan Jun 12, 2018
a53b317
Updated server to allow multiple databases by URL which is saved in t…
lukeaduncan Jun 12, 2018
1ec565b
Merge branch 'master' of github.com:intrahealth/hearth
lukeaduncan Jun 12, 2018
1f7b149
Fixed relation links to include the database name in the URL when it …
lukeaduncan Jun 21, 2018
e132c3d
Added in $hierarchy operation for Location to return all the children…
lukeaduncan Jun 22, 2018
6638aeb
Added in operation to do the reverse order of . (i.e. to return all…
lukeaduncan Jun 22, 2018
c5c8eb3
Updated and operations to also return the requested/starting Location.
lukeaduncan Jun 22, 2018
cf86abb
Updated hierarchy/parent operation to add an additional field to the …
lukeaduncan Jun 25, 2018
cd29894
Updated matching queue to allow a list of databses to create workers …
lukeaduncan Jun 25, 2018
6c513b8
Added in the allowDiskUse option for the hiearchy aggregation.
lukeaduncan Jun 27, 2018
ee4b8bf
Updated hierarchy/parents operation for Location. Instead of using a…
lukeaduncan Jul 11, 2018
df9a54f
Added _tag to Location for queries.
lukeaduncan Jul 24, 2018
075e13f
Updated dependencies.
lukeaduncan Aug 29, 2018
7765333
Further dependency updates.
lukeaduncan Aug 29, 2018
2a9a8d1
push to dockerhub script
citizenrich Oct 13, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion lib/atna-audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const ATNAAuditConfig = require('./config').getConf('atnaAudit')
let connDetail = {
interface: ATNAAuditConfig.interface,
host: ATNAAuditConfig.host,
port: ATNAAuditConfig.port
port: ATNAAuditConfig.port,
options: {}
}

if (ATNAAuditConfig.certOptions.key) {
Expand Down Expand Up @@ -110,6 +111,47 @@ exports.buildPDQmAuditMsg = (ctx) => {
return syslog
}

exports.buildmCSDAuditMsg = (ctx, transaction, actor) => {
// event
var eventID = new ATNA.construct.Code(110112, 'Query', 'DCM')
var typeCode = new ATNA.construct.Code(transaction, 'Mobile Care Services Discovery '+actor+' Query', 'IHE Transactions')
var eIdent = new ATNA.construct.EventIdentification(ATNA.constants.EVENT_ACTION_EXECUTE, new Date(), ATNA.constants.OUTCOME_SUCCESS, eventID, typeCode)

var user = ctx.authenticatedUser.email;
// Active Participant - System
var sysRoleCode = new ATNA.construct.Code(110153, 'Source', 'DCM')
var sysParticipant = new ATNA.construct.ActiveParticipant(user, '', false, ctx.requestorIp, ATNA.constants.NET_AP_TYPE_IP, [sysRoleCode])

// Active Participant - User
var userRoleCodeDef = new ATNA.construct.Code(110152, 'DCM', 'Destination')
var userParticipant = new ATNA.construct.ActiveParticipant(user, '', true, null, null, [userRoleCodeDef])

// Audit Source
var sourceTypeCode = new ATNA.construct.Code(ATNA.constants.AUDIT_SRC_TYPE_WEB_SERVER, '', '')
var sourceIdent = new ATNA.construct.AuditSourceIdentification(null, ctx.authenticatedUser, sourceTypeCode)

// Participant Object
var objIdTypeCode = new ATNA.construct.Code(transaction, 'Mobile Care Services Discovery '+actor+' Query', 'IHE Transactions')
var participantObj = new ATNA.construct.ParticipantObjectIdentification(
ctx.fullUrl,
ATNA.constants.OBJ_TYPE_SYS_OBJ,
ATNA.constants.OBJ_TYPE_CODE_ROLE_QUERY,
null,
null,
objIdTypeCode,
null,
new Buffer(ctx.fullUrl).toString('base64')
)

var audit = new ATNA.construct.AuditMessage(eIdent, [sysParticipant, userParticipant], [participantObj], [sourceIdent])
var xml = audit.toXML()

const syslog = ATNA.construct.wrapInSyslog(xml)

return syslog
}


exports.sendAuditEvent = (msg, callback) => {
if (!ATNAAuditConfig.enabled) {
return
Expand Down
13 changes: 10 additions & 3 deletions lib/fhir/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,9 +360,16 @@ module.exports = exports = (mongo) => {
if (entry._mpi) {
search = entry._mpi.search
}
if ( entry._request.method == 'POST' ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: we should always use triple equals ===

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check indentation
remove spaces within condition braces
update condition to be ===

entry._request.url = entry.resourceType
} else {
entry._request.url = entry.resourceType+"/"+entry.id
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update variable to be defined in an template Literal way, making it easier to read
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals

}

const ret = {
fullUrl: `${config.getConf('server:publicFhirBase')}/${entry.resourceType}/${entry.id}`,
resource: entry
fullUrl: `${config.getConf('server:publicFhirBase')}/${entry.resourceType}/${entry.id}/_history/${entry.meta.versionId}`,
resource: entry,
request: entry._request
}

if (search) {
Expand Down Expand Up @@ -543,7 +550,7 @@ module.exports = exports = (mongo) => {
*/
getPagingParams: (queryParams, callback) => {
let _getpagesoffset = 0
let _count = 10
let _count = 100
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this to the default of 10. It can always be overridden by the client.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be reverted to the default of 10, A query parameter (_count) can be sent to increase the paging count, If this is set to _count=0 then all results will be returned


if (queryParams._getpagesoffset) {
_getpagesoffset = parseInt(queryParams._getpagesoffset)
Expand Down
201 changes: 183 additions & 18 deletions lib/fhir/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
*/
'use strict'

const moment = require('moment')
const logger = require('winston')

const FhirCommon = require('./common')
const Authorization = require('../security/authorization')
const Hooks = require('./hooks')
const matchingConfig = require('../../config/matching')
const QueryUtils = require('./query-utils');

const handleErrorAndBadRequest = (err, badRequest, callback) => {
if (err) {
Expand All @@ -30,6 +32,7 @@ const handleErrorAndBadRequest = (err, badRequest, callback) => {
module.exports = (mongo, modules) => {
const fhirCommon = FhirCommon(mongo)
const authorization = Authorization(mongo)
const queryUtils = QueryUtils(mongo)
const hooks = Hooks()

return {
Expand Down Expand Up @@ -321,6 +324,133 @@ module.exports = (mongo, modules) => {
})
},

/**
* History for a resource. The 'query' field must be specified in the RequestContext parameter.
*
* @param {RequestContext} ctx
* @param {String} resourceType The type of FHIR resource
* @param {CoreCallback} callback
*/
history: (ctx, resourceType, callback) => {
authorization.authorize('history', ctx, resourceType, (err, badRequest) => {
if (err || badRequest) {
return handleErrorAndBadRequest(err, badRequest, callback)
}

const fhirModule = modules[resourceType]
hooks.executeBeforeHooks('history', fhirModule, ctx, resourceType, (err, badRequest) => {
if (err || badRequest) {
return handleErrorAndBadRequest(err, badRequest, callback)
}

mongo.getDB((err, db) => {
if (err) {
return callback(err)
}

/* This doesn't work for deletes but saving in case needed later.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove commented out code

let query = [ { $lookup : { from : resourceType+'_history', localField: "id", foreignField: "id", as: 'history' } },
{ $lookup : { from: resourceType, localField: 'id', foreignField: 'id', as : 'top' } },
{ $project : { allHist :{ $setUnion : ["$top","$history"] } } },
{ $unwind : "$allHist" },
{$replaceRoot: {newRoot:"$allHist"}} ]
*/
let query = [
{ $limit: 1 },
{ $lookup: { from: resourceType, pipeline:[], as: "top" } },
{ $lookup: { from: resourceType+"_history", pipeline: [], as: "history" } },
{ $project: { all: { $setUnion: [ "$top", "$history" ] } } },
{ $unwind: "$all" },
{ $replaceRoot: { newRoot: "$all" } }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps another way to do this would be to just to two separate queries and combine the results? Might be more efficient. Although this way seems fine to until we actually see a performance issue.

]

if ( ctx.query._since ) {
const matchClause = {$match: queryUtils.dateToMongoClause('meta.lastUpdated', 'ge'+ctx.query._since) }
query[1].$lookup.pipeline.push( matchClause )
query[2].$lookup.pipeline.push( matchClause )
}

let cntQuery = query.slice(0,query.length)
cntQuery.push( {$count: 'total'} )

query.push( {$sort: {'meta.lastUpdated':-1} } )


mongo.util.debugLog(resourceType, 'history', query)

fhirCommon.getPagingParams(ctx.query, (err, badRequest, _getpagesoffset, _count) => {
if (err || badRequest) {
return handleErrorAndBadRequest(err, badRequest, callback)
}

const c = db.collection(resourceType)
c.aggregate(cntQuery, (err, total) => {
if (err) {
return callback(err)
}
if ( total.length == 0 ) {
const response = fhirCommon.bundleResults('history', [], 0, ctx.url)
hooks.executeAfterHooks('history', fhirModule, ctx, resourceType, response, (err, badRequest, results) => {
if (err || badRequest) {
return handleErrorAndBadRequest(err, badRequest, callback)
}

// format the updated entry array for the response
if (results && results.entry) {
results.entry.forEach((resource) => {
fhirCommon.formatResource(resource.resource)
})
}

callback(null, { httpStatus: 200, resource: results })
})
} else {
total = total[0].total


c.aggregate(query).skip(_getpagesoffset).limit(_count).toArray((err, results) => {
if (err) {
return callback(err)
}


const response = fhirCommon.bundleResults('history', results, total, ctx.url)
if (_getpagesoffset + _count < total) {
fhirCommon.addBundleLinkNext(response, ctx.url, _getpagesoffset + _count, _count)
}

hooks.executeAfterHooks('history', fhirModule, ctx, resourceType, response, (err, badRequest, results) => {
if (err || badRequest) {
return handleErrorAndBadRequest(err, badRequest, callback)
}

// format the updated entry array for the response
if (results && results.entry) {
results.entry.forEach((resource) => {
fhirCommon.formatResource(resource.resource)
})
}

callback(null, { httpStatus: 200, resource: results })
})


})// find

}


})
})


}) //mongo.getdb.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove commented code

})
})
},



/**
* Create a new resource.
*
Expand All @@ -342,10 +472,13 @@ module.exports = (mongo, modules) => {
}

const fhirModule = modules[resourceType]
hooks.executeBeforeHooks('create', fhirModule, ctx, resourceType, resource, (err, badRequest) => {
hooks.executeBeforeHooks('create', fhirModule, ctx, resourceType, resource, (err, badRequest, newResource) => {
if (err || badRequest) {
return handleErrorAndBadRequest(err, badRequest, callback)
}
if ( newResource ) {
resource = newResource;
}

if (!resource.resourceType || resource.resourceType !== resourceType) {
return callback(null, fhirCommon.buildHTTPOutcome(400, 'error', 'invalid', 'Invalid resource type'))
Expand Down Expand Up @@ -406,10 +539,13 @@ module.exports = (mongo, modules) => {
}

const fhirModule = modules[resourceType]
hooks.executeBeforeHooks('update', fhirModule, ctx, resourceType, resource, (err, badRequest) => {
hooks.executeBeforeHooks('update', fhirModule, ctx, resourceType, resource, (err, badRequest, newResource) => {
if (err || badRequest) {
return handleErrorAndBadRequest(err, badRequest, callback)
}
if ( newResource ) {
resource = newResource;
}
if (!resource.resourceType || resource.resourceType !== resourceType) {
return callback(null, fhirCommon.buildHTTPOutcome(400, 'error', 'invalid', 'Invalid resource type'))
}
Expand All @@ -430,7 +566,7 @@ module.exports = (mongo, modules) => {
resource.meta.versionId = fhirCommon.util.generateID()

const options = {
upsert: false, // TODO OHIE-168
upsert: true, // TODO OHIE-168
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make this configurable via a config option with a default of false.

returnOriginal: true
}

Expand All @@ -441,16 +577,6 @@ module.exports = (mongo, modules) => {
}

if (!result.value) {
return callback(null, fhirCommon.buildHTTPOutcome(404, 'information', 'not-found', 'Not found'))
}

delete result.value._id

const cHistory = db.collection(`${resourceType}_history`)
cHistory.insert(result.value, (err) => {
if (err) {
return callback(err)
}
hooks.executeAfterHooks('update', fhirModule, ctx, resourceType, resource, (err, badRequest) => {
if (err || badRequest) {
return handleErrorAndBadRequest(err, badRequest, callback)
Expand All @@ -459,7 +585,26 @@ module.exports = (mongo, modules) => {
const location = `/fhir/${resourceType}/${id}/_history/${resource.meta.versionId}`
callback(null, { httpStatus: 200, location: location, id: id })
})
})
//return callback(null, fhirCommon.buildHTTPOutcome(404, 'information', 'not-found', 'Not found'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we still need this instead of the callback at line 586?

} else {

delete result.value._id

const cHistory = db.collection(`${resourceType}_history`)
cHistory.insert(result.value, (err) => {
if (err) {
return callback(err)
}
hooks.executeAfterHooks('update', fhirModule, ctx, resourceType, resource, (err, badRequest) => {
if (err || badRequest) {
return handleErrorAndBadRequest(err, badRequest, callback)
}

const location = `/fhir/${resourceType}/${id}/_history/${resource.meta.versionId}`
callback(null, { httpStatus: 200, location: location, id: id })
})
})
}
})
})
})
Expand Down Expand Up @@ -510,13 +655,33 @@ module.exports = (mongo, modules) => {
if (err) {
return callback(err)
}
hooks.executeAfterHooks('delete', fhirModule, ctx, resourceType, historyDoc, (err, badRequest) => {
if (err || badRequest) {
return handleErrorAndBadRequest(err, badRequest, callback)

// Also need to add a deleted record to the history.
const dateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ'
const deleteDoc = {
id: historyDoc.id,
resourceType: historyDoc.resourceType,
_request: { method: "DELETE" },
meta: { lastUpdated: moment().format(dateFormat) }
}

cHistory.insert(deleteDoc, (err) => {
if (err) {
return callback(err)
}

callback(null, { httpStatus: 204, id: id })
hooks.executeAfterHooks('delete', fhirModule, ctx, resourceType, historyDoc, (err, badRequest) => {
if (err || badRequest) {
return handleErrorAndBadRequest(err, badRequest, callback)
}

callback(null, { httpStatus: 204, id: id })
})

})



})
} else {
cHistory.remove({ id: id }, (err) => {
Expand Down
2 changes: 1 addition & 1 deletion lib/fhir/module-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function getLoadedModule (resourceType) {
module.exports = exports = (mongo) => {
return {
loadModules: () => {
fs.readdirSync(path.resolve(`${global.appRoot}/lib/fhir/resources`)).forEach((file) => {
fs.readdirSync(path.resolve(`${global.appRoot}/lib/fhir/resources`)).filter(function(file) { return path.extname(file) === '.js' } ).forEach((file) => {
let module = require(`./resources/${file}`)(mongo)
fhirResources[module.name] = module
logger.info(`Loaded FHIR resource module: ${module.name}`)
Expand Down
Loading