parent
6425ea48f0
commit
7f8e592f6a
|
@ -1,6 +1,9 @@
|
|||
import {BaseClient} from './base_client';
|
||||
import {Source} from '../../../models/database/source';
|
||||
|
||||
// @ts-ignore
|
||||
import * as BaseClient_GetRequest from './fixtures/BaseClient_GetRequest.json';
|
||||
// @ts-ignore
|
||||
import * as BaseClient_GetFhirVersion from './fixtures/BaseClient_GetFhirVersion.json';
|
||||
|
||||
|
||||
|
|
|
@ -26,6 +26,11 @@ export abstract class BaseClient {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function gets the FhirVersion as specified by the api CapabilityStatement endpoint (metadata)
|
||||
* https://build.fhir.org/capabilitystatement.html
|
||||
* @constructor
|
||||
*/
|
||||
public async GetFhirVersion(): Promise<any> {
|
||||
return this.GetRequest("metadata")
|
||||
.then((resp) => {
|
||||
|
@ -33,6 +38,12 @@ export abstract class BaseClient {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will make an authenticated request against an OAuth protected resource. If the AccessToken used has expired, it will attempt
|
||||
* to use a refresh token (if present) to get a new AccessToken.
|
||||
* @param resourceSubpathOrNext
|
||||
* @constructor
|
||||
*/
|
||||
public async GetRequest(resourceSubpathOrNext: string): Promise<any> {
|
||||
|
||||
//check if the url is absolute
|
||||
|
@ -63,13 +74,6 @@ export abstract class BaseClient {
|
|||
// Protected methods
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected GetPatientBundle(patientId: string): Promise<any> {
|
||||
return this.GetRequest(`Patient/${patientId}/$everything`)
|
||||
}
|
||||
|
||||
protected GetPatient(patientId: string): Promise<IResourceRaw> {
|
||||
return this.GetRequest(`Patient/${patientId}`)
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Private methods
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import {FHIR401Client} from './fhir401_r4_client';
|
||||
import {Source} from '../../../models/database/source';
|
||||
import * as FHIR401Client_ProcessBundle from './fixtures/FHIR401Client_ProcessBundle.json';
|
||||
import {IResourceBundleRaw} from '../../interface';
|
||||
import {ResourceFhir} from '../../../models/database/resource_fhir';
|
||||
import {NewRepositiory} from '../../../database/pouchdb_repository';
|
||||
import {Base64} from '../../../utils/base64';
|
||||
|
||||
// @ts-ignore
|
||||
import * as FHIR401Client_ProcessBundle from './fixtures/FHIR401Client_ProcessBundle.json';
|
||||
|
||||
class TestClient extends FHIR401Client {
|
||||
constructor(source: Source) {
|
||||
super(source);
|
||||
|
|
|
@ -13,28 +13,106 @@ export class FHIR401Client extends BaseClient implements IClient {
|
|||
super(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function attempts to retrieve a Patient Bundle and sync all resources to the database
|
||||
* @param db
|
||||
* @constructor
|
||||
*/
|
||||
public async SyncAll(db: IDatabaseRepository): Promise<string[]> {
|
||||
const bundle = await this.GetPatientBundle(this.source.patient)
|
||||
|
||||
const wrappedResourceModels = await this.ProcessBundle(bundle)
|
||||
//todo, create the resources in dependency order
|
||||
|
||||
//TODO bulk insert
|
||||
// for(let dbModel of wrappedResourceModels){
|
||||
// db.CreateResource(dbModel)
|
||||
// }
|
||||
console.log(wrappedResourceModels)
|
||||
return db.CreateResources(wrappedResourceModels)
|
||||
|
||||
}
|
||||
|
||||
public async SyncAllBundle(db: IDatabaseRepository, bundleFile: any): Promise<any> {
|
||||
return Promise.resolve(undefined);
|
||||
/**
|
||||
* If Patient-everything api endpoint is unavailable (SyncAll) this function can be used to search for each resource associated with a Patient
|
||||
* and sync them to the database.
|
||||
* @param db
|
||||
* @param resourceNames
|
||||
* @constructor
|
||||
*/
|
||||
public async SyncAllByResourceName(db: IDatabaseRepository, resourceNames: string[]): Promise<string[]>{
|
||||
//Store the Patient
|
||||
const patientResource = await this.GetPatient(this.source.patient)
|
||||
|
||||
const patientResourceFhir = new ResourceFhir()
|
||||
patientResourceFhir.source_id = this.source._id
|
||||
patientResourceFhir.source_resource_type = patientResource.resourceType
|
||||
patientResourceFhir.source_resource_id = patientResource.id
|
||||
patientResourceFhir.resource_raw = patientResource
|
||||
|
||||
await db.CreateResource(patientResourceFhir)
|
||||
|
||||
//error map storage.
|
||||
let syncErrors = {}
|
||||
|
||||
//Store all other resources.
|
||||
for(let resourceType of resourceNames) {
|
||||
|
||||
try {
|
||||
let bundle = await this.GetResourceBundlePaginated(`${resourceType}?patient=${this.source.patient}`)
|
||||
let wrappedResourceModels = await this.ProcessBundle(bundle)
|
||||
|
||||
for(let apiModel of wrappedResourceModels){
|
||||
await db.CreateResource(apiModel)
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`An error occurred while processing ${resourceType} bundle ${this.source.patient}`)
|
||||
syncErrors[resourceType] = e
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: correctly return newly inserted documents
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to sync all resources from a Bundle file. Not applicable to this Client
|
||||
* @param db
|
||||
* @param bundleFile
|
||||
* @constructor
|
||||
*/
|
||||
public async SyncAllFromBundleFile(db: IDatabaseRepository, bundleFile: any): Promise<any> {
|
||||
return Promise.reject(new Error("not implemented"));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Protected methods
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* This function attempts to retrieve the Patient Bundle using the Patient-everything api endpoint (if available)
|
||||
* This response may be paginated
|
||||
* https://hl7.org/fhir/operation-patient-everything.html
|
||||
* @param patientId
|
||||
* @constructor
|
||||
* @protected
|
||||
*/
|
||||
protected GetPatientBundle(patientId: string): Promise<any> {
|
||||
return this.GetResourceBundlePaginated(`Patient/${patientId}/$everything`)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function retrieves a patient resource
|
||||
* @param patientId
|
||||
* @constructor
|
||||
* @protected
|
||||
*/
|
||||
protected GetPatient(patientId: string): Promise<IResourceRaw> {
|
||||
return this.GetRequest(`Patient/${patientId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function parses a FHIR Bundle and wraps each BundleEntry resource in a ResourceFhir object which can be stored in the DB.
|
||||
* @param bundle
|
||||
* @constructor
|
||||
* @protected
|
||||
*/
|
||||
protected async ProcessBundle(bundle: IResourceBundleRaw): Promise<ResourceFhir[]> {
|
||||
// console.log(bundle)
|
||||
// process each entry in bundle
|
||||
|
@ -52,11 +130,55 @@ export class FHIR401Client extends BaseClient implements IClient {
|
|||
// wrappedResourceModel.updated_at = bundleEntry.resource.meta?.lastUpdated
|
||||
return wrappedResourceModel
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Private methods
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Retrieve a resource bundle. While "next" link is present in response, continue to request urls and append BundleEntries
|
||||
* @param relativeResourcePath
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
private async GetResourceBundlePaginated(relativeResourcePath: string): Promise<IResourceBundleRaw> {
|
||||
// https://www.hl7.org/fhir/patient-operation-everything.html
|
||||
|
||||
const bundle = await this.GetRequest(relativeResourcePath) as IResourceBundleRaw
|
||||
|
||||
let next: string
|
||||
let prev: string
|
||||
let self: string
|
||||
for(let link of bundle.link || []){
|
||||
if(link.relation == "next"){
|
||||
next = link.url
|
||||
} else if(link.relation == "self"){
|
||||
self = link.url
|
||||
} else if(link.relation == "previous"){
|
||||
prev = link.url
|
||||
}
|
||||
}
|
||||
|
||||
while(next && next != self && next != prev){
|
||||
console.debug(`Paginated request => ${next}`)
|
||||
let nextBundle = await this.GetRequest(next) as IResourceBundleRaw
|
||||
bundle.entry = bundle.entry.concat(nextBundle.entry)
|
||||
|
||||
next = "" //reset the pointers
|
||||
self = ""
|
||||
prev = ""
|
||||
for(let link of nextBundle.link){
|
||||
if(link.relation == "next"){
|
||||
next = link.url
|
||||
} else if(link.relation == "self"){
|
||||
self = link.url
|
||||
} else if(link.relation == "previous"){
|
||||
prev = link.url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bundle
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,19 @@ export interface IClient {
|
|||
fhirVersion: string
|
||||
GetRequest(resourceSubpath: string): Promise<any>
|
||||
GetFhirVersion(): Promise<any>
|
||||
|
||||
/**
|
||||
* This function attempts to retrieve a Patient Bundle and sync all resources to the database
|
||||
* @param db
|
||||
* @constructor
|
||||
*/
|
||||
SyncAll(db: IDatabaseRepository): Promise<any>
|
||||
|
||||
|
||||
SyncAllByResourceName(db: IDatabaseRepository, resourceNames: string[]): Promise<string[]>
|
||||
|
||||
//Manual client ONLY functions
|
||||
SyncAllBundle(db: IDatabaseRepository, bundleFile: any): Promise<any>
|
||||
SyncAllFromBundleFile(db: IDatabaseRepository, bundleFile: any): Promise<any>
|
||||
}
|
||||
|
||||
//This is the "raw" Fhir resource
|
||||
|
|
Loading…
Reference in New Issue