provide a consistent way to retrun results from WebWorkers.
fixed Username field in login page. added support for CORS relay fixed spec files.
This commit is contained in:
parent
6af72266f6
commit
4f90a9eedb
|
@ -23,3 +23,9 @@ docker run --rm -it -p 5984:5984 -v `pwd`/.couchdb/data:/opt/couchdb/data -v `pw
|
||||||
- WebUI:
|
- WebUI:
|
||||||
- username: `testuser`
|
- username: `testuser`
|
||||||
- password: `testuser`
|
- password: `testuser`
|
||||||
|
|
||||||
|
|
||||||
|
# Running tests
|
||||||
|
|
||||||
|
- ng test --include='**/base_client.spec.ts'
|
||||||
|
- ng test --include='lib/**/*.spec.ts'
|
||||||
|
|
|
@ -6,6 +6,4 @@ Find & replace the following
|
||||||
- `fastenhealth` - find and replace this with your binary name
|
- `fastenhealth` - find and replace this with your binary name
|
||||||
- make sure you rename the folder as well.
|
- make sure you rename the folder as well.
|
||||||
|
|
||||||
# Running tests
|
|
||||||
|
|
||||||
- ng test --include='**/base_client.spec.ts'
|
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//TODO, there are security implications to this, we need to make sure we lock this down.
|
||||||
|
func CORSProxy(c *gin.Context) {
|
||||||
|
//appConfig := c.MustGet("CONFIG").(config.Interface)
|
||||||
|
corsUrl := fmt.Sprintf("https://%s", strings.TrimPrefix(c.Param("proxyPath"), "/"))
|
||||||
|
|
||||||
|
remote, err := url.Parse(corsUrl)
|
||||||
|
remote.RawQuery = c.Request.URL.Query().Encode()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := httputil.ReverseProxy{}
|
||||||
|
//Define the director func
|
||||||
|
//This is a good place to log, for example
|
||||||
|
proxy.Director = func(req *http.Request) {
|
||||||
|
req.Header = c.Request.Header
|
||||||
|
req.Header.Add("X-Forwarded-Host", req.Host)
|
||||||
|
req.Header.Add("X-Origin-Host", remote.Host)
|
||||||
|
req.Host = remote.Host
|
||||||
|
req.URL.Scheme = remote.Scheme
|
||||||
|
req.URL.Host = remote.Host
|
||||||
|
log.Printf(c.Param("proxyPath"))
|
||||||
|
req.URL.Path = remote.Path
|
||||||
|
|
||||||
|
//TODO: throw an error if the remote.Host is not allowed
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.ModifyResponse = func(r *http.Response) error {
|
||||||
|
//b, _ := ioutil.ReadAll(r.Body)
|
||||||
|
//buf := bytes.NewBufferString("Monkey")
|
||||||
|
//buf.Write(b)
|
||||||
|
//r.Body = ioutil.NopCloser(buf)
|
||||||
|
r.Header.Set("Access-Control-Allow-Methods", "GET,HEAD")
|
||||||
|
r.Header.Set("Access-Control-Allow-Credentials", "true")
|
||||||
|
r.Header.Set("Access-Control-Allow-Origin", "*")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.ServeHTTP(c.Writer, c.Request)
|
||||||
|
}
|
|
@ -44,6 +44,8 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
|
||||||
api.GET("/metadata/source", handler.GetMetadataSource)
|
api.GET("/metadata/source", handler.GetMetadataSource)
|
||||||
|
|
||||||
r.Any("/database/*proxyPath", handler.CouchDBProxy)
|
r.Any("/database/*proxyPath", handler.CouchDBProxy)
|
||||||
|
r.GET("/cors/*proxyPath", handler.CORSProxy)
|
||||||
|
r.OPTIONS("/cors/*proxyPath", handler.CORSProxy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,6 @@ export class SourceSyncMessage {
|
||||||
source: Source
|
source: Source
|
||||||
userIdentifier: string
|
userIdentifier: string
|
||||||
encryptionKey?: string
|
encryptionKey?: string
|
||||||
|
|
||||||
|
response?: any
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,15 @@
|
||||||
|
|
||||||
<form (ngSubmit)="signinSubmit()" #userForm="ngForm">
|
<form (ngSubmit)="signinSubmit()" #userForm="ngForm">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Email</label>
|
<label>Username</label>
|
||||||
<input [(ngModel)]="existingUser.username" name="username" #username="ngModel" required minlength="2" type="text" class="form-control" placeholder="Enter your email">
|
<input [(ngModel)]="existingUser.username" name="username" #username="ngModel" required minlength="2" type="text" class="form-control" placeholder="Enter your username">
|
||||||
|
|
||||||
<div *ngIf="username.invalid && (username.dirty || username.touched)" class="alert alert-danger">
|
<div *ngIf="username.invalid && (username.dirty || username.touched)" class="alert alert-danger">
|
||||||
<div *ngIf="username.errors?.['required']">
|
<div *ngIf="username.errors?.['required']">
|
||||||
Email is required.
|
Username is required.
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="username.errors?.['minlength']">
|
<div *ngIf="username.errors?.['minlength']">
|
||||||
Email must be at least 4 characters long.
|
Username must be at least 4 characters long.
|
||||||
</div>
|
|
||||||
<div *ngIf="username.errors?.['email']">
|
|
||||||
Email is not a valid email address.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- form-group -->
|
</div><!-- form-group -->
|
||||||
|
|
|
@ -14,6 +14,8 @@ import {SourceType} from '../../../lib/models/database/source_types';
|
||||||
import {QueueService} from '../../workers/queue.service';
|
import {QueueService} from '../../workers/queue.service';
|
||||||
import {ToastService} from '../../services/toast.service';
|
import {ToastService} from '../../services/toast.service';
|
||||||
import {ToastNotification, ToastType} from '../../models/fasten/toast';
|
import {ToastNotification, ToastType} from '../../models/fasten/toast';
|
||||||
|
import {SourceSyncMessage} from '../../models/queue/source-sync-message';
|
||||||
|
import {UpsertSummary} from '../../../lib/models/fasten/upsert-summary';
|
||||||
// If you dont import this angular will import the wrong "Location"
|
// If you dont import this angular will import the wrong "Location"
|
||||||
|
|
||||||
export const sourceConnectWindowTimeout = 24*5000 //wait 2 minutes (5 * 24 = 120)
|
export const sourceConnectWindowTimeout = 24*5000 //wait 2 minutes (5 * 24 = 120)
|
||||||
|
@ -192,7 +194,7 @@ export class MedicalSourcesComponent implements OnInit {
|
||||||
expires_at: parseInt(getAccessTokenExpiration(payload, new BrowserAdapter())),
|
expires_at: parseInt(getAccessTokenExpiration(payload, new BrowserAdapter())),
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.fastenDb.CreateSource(dbSourceCredential).then(console.log)
|
await this.fastenDb.UpsertSource(dbSourceCredential).then(console.log)
|
||||||
this.queueSourceSyncWorker(sourceType as SourceType, dbSourceCredential)
|
this.queueSourceSyncWorker(sourceType as SourceType, dbSourceCredential)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -245,29 +247,40 @@ export class MedicalSourcesComponent implements OnInit {
|
||||||
// so that we can show incompelte statuses
|
// so that we can show incompelte statuses
|
||||||
this.queueService.runSourceSyncWorker(source)
|
this.queueService.runSourceSyncWorker(source)
|
||||||
.subscribe((msg) => {
|
.subscribe((msg) => {
|
||||||
const sourceSyncMessage = JSON.parse(msg)
|
const sourceSyncMessage = JSON.parse(msg) as SourceSyncMessage
|
||||||
delete this.status[sourceType]
|
delete this.status[sourceType]
|
||||||
// window.location.reload();
|
// window.location.reload();
|
||||||
|
|
||||||
console.log("source sync-all response:", sourceSyncMessage)
|
console.log("source sync-all response:", sourceSyncMessage)
|
||||||
//remove item from available sources list, add to connected sources.
|
//remove item from available sources list, add to connected sources.
|
||||||
this.availableSourceList.splice(this.availableSourceList.findIndex((item) => item.metadata.source_type == sourceType), 1);
|
this.availableSourceList.splice(this.availableSourceList.findIndex((item) => item.metadata.source_type == sourceType), 1);
|
||||||
this.connectedSourceList.push({source: sourceSyncMessage.source, metadata: this.metadataSources[sourceType]})
|
if(this.connectedSourceList.findIndex((item) => item.metadata.source_type == sourceType) == -1){
|
||||||
|
//only add this as a connected source if its "new"
|
||||||
|
this.connectedSourceList.push({source: sourceSyncMessage.source, metadata: this.metadataSources[sourceType]})
|
||||||
|
}
|
||||||
|
|
||||||
const toastNotificaiton = new ToastNotification()
|
const toastNotification = new ToastNotification()
|
||||||
toastNotificaiton.type = ToastType.Success
|
toastNotification.type = ToastType.Success
|
||||||
toastNotificaiton.message = `Successfully connected ${sourceType}`
|
toastNotification.message = `Successfully connected ${sourceType}`
|
||||||
this.toastService.show(toastNotificaiton)
|
|
||||||
|
const upsertSummary = sourceSyncMessage.response as UpsertSummary
|
||||||
|
if(upsertSummary && upsertSummary.totalResources != upsertSummary.updatedResources.length){
|
||||||
|
toastNotification.message += `\n (total: ${upsertSummary.totalResources}, updated: ${upsertSummary.updatedResources.length})`
|
||||||
|
} else if(upsertSummary){
|
||||||
|
toastNotification.message += `\n (total: ${upsertSummary.totalResources})`
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toastService.show(toastNotification)
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
delete this.status[sourceType]
|
delete this.status[sourceType]
|
||||||
// window.location.reload();
|
// window.location.reload();
|
||||||
|
|
||||||
const toastNotificaiton = new ToastNotification()
|
const toastNotification = new ToastNotification()
|
||||||
toastNotificaiton.type = ToastType.Error
|
toastNotification.type = ToastType.Error
|
||||||
toastNotificaiton.message = `An error occurred while accessing ${sourceType}: ${err}`
|
toastNotification.message = `An error occurred while accessing ${sourceType}: ${err}`
|
||||||
toastNotificaiton.autohide = false
|
toastNotification.autohide = false
|
||||||
this.toastService.show(toastNotificaiton)
|
this.toastService.show(toastNotification)
|
||||||
console.error(err)
|
console.error(err)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,14 @@ export class SourceSyncWorker implements DoWork<string, string> {
|
||||||
const client = NewClient(sourceSyncMessage.source.source_type, sourceSyncMessage.source)
|
const client = NewClient(sourceSyncMessage.source.source_type, sourceSyncMessage.source)
|
||||||
//TODO: validate the FHIR version from the datasource matches the client
|
//TODO: validate the FHIR version from the datasource matches the client
|
||||||
// if the source token has been refreshed, we need to store it in the DB.
|
// if the source token has been refreshed, we need to store it in the DB.
|
||||||
// await db.CreateSource()
|
// await db.UpsertSource()
|
||||||
|
|
||||||
console.log("!!!!!!!!!!!!!!STARTING WORKER SYNC!!!!!!!!!", sourceSyncMessage)
|
console.log("!!!!!!!!!!!!!!STARTING WORKER SYNC!!!!!!!!!", sourceSyncMessage)
|
||||||
return client.SyncAll(db)
|
return client.SyncAll(db)
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
console.log("!!!!!!!!!!!!!COMPLETE WORKER SYNC!!!!!!!!!!", resp)
|
console.log("!!!!!!!!!!!!!COMPLETE WORKER SYNC!!!!!!!!!!", resp)
|
||||||
return JSON.stringify(resp)
|
sourceSyncMessage.response = resp
|
||||||
|
return JSON.stringify(sourceSyncMessage)
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {IClient} from '../interface';
|
||||||
import {FHIR401Client} from './base/fhir401_r4_client';
|
import {FHIR401Client} from './base/fhir401_r4_client';
|
||||||
import {Source} from '../../models/database/source';
|
import {Source} from '../../models/database/source';
|
||||||
import {IDatabaseRepository} from '../../database/interface';
|
import {IDatabaseRepository} from '../../database/interface';
|
||||||
|
import {UpsertSummary} from '../../models/fasten/upsert-summary';
|
||||||
|
|
||||||
export class AetnaClient extends FHIR401Client implements IClient {
|
export class AetnaClient extends FHIR401Client implements IClient {
|
||||||
constructor(source: Source) {
|
constructor(source: Source) {
|
||||||
|
@ -13,11 +14,11 @@ export class AetnaClient extends FHIR401Client implements IClient {
|
||||||
* @param db
|
* @param db
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
async SyncAll(db: IDatabaseRepository): Promise<string[]> {
|
async SyncAll(db: IDatabaseRepository): Promise<UpsertSummary> {
|
||||||
const bundle = await this.GetResourceBundlePaginated("Patient")
|
const bundle = await this.GetResourceBundlePaginated("Patient")
|
||||||
|
|
||||||
const wrappedResourceModels = await this.ProcessBundle(bundle)
|
const wrappedResourceModels = await this.ProcessBundle(bundle)
|
||||||
//todo, create the resources in dependency order
|
//todo, create the resources in dependency order
|
||||||
return await db.CreateResources(wrappedResourceModels)
|
return await db.UpsertResources(wrappedResourceModels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {IClient} from '../interface';
|
||||||
import {FHIR401Client} from './base/fhir401_r4_client';
|
import {FHIR401Client} from './base/fhir401_r4_client';
|
||||||
import {Source} from '../../models/database/source';
|
import {Source} from '../../models/database/source';
|
||||||
import {IDatabaseRepository} from '../../database/interface';
|
import {IDatabaseRepository} from '../../database/interface';
|
||||||
|
import {UpsertSummary} from '../../models/fasten/upsert-summary';
|
||||||
|
|
||||||
export class AthenaClient extends FHIR401Client implements IClient {
|
export class AthenaClient extends FHIR401Client implements IClient {
|
||||||
constructor(source: Source) {
|
constructor(source: Source) {
|
||||||
|
@ -13,7 +14,7 @@ export class AthenaClient extends FHIR401Client implements IClient {
|
||||||
* @param db
|
* @param db
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
async SyncAll(db: IDatabaseRepository): Promise<string[]> {
|
async SyncAll(db: IDatabaseRepository): Promise<UpsertSummary> {
|
||||||
const supportedResources: string[] = [
|
const supportedResources: string[] = [
|
||||||
"AllergyIntolerance",
|
"AllergyIntolerance",
|
||||||
//"Binary",
|
//"Binary",
|
||||||
|
|
|
@ -55,6 +55,13 @@ export abstract class BaseClient {
|
||||||
} else {
|
} else {
|
||||||
resourceUrl = resourceSubpathOrNext
|
resourceUrl = resourceSubpathOrNext
|
||||||
}
|
}
|
||||||
|
if(this.source.cors_relay_required){
|
||||||
|
//this endpoint requires a CORS relay
|
||||||
|
//get the path to the Fasten server, and append `cors/` and then append the request url
|
||||||
|
let resourceParts = new URL(resourceUrl)
|
||||||
|
resourceUrl = this.getCORSProxyPath() + `${resourceParts.hostname}${resourceParts.pathname}${resourceParts.search}`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//refresh the source if required
|
//refresh the source if required
|
||||||
this.source = await this.refreshExpiredTokenIfRequired(this.source)
|
this.source = await this.refreshExpiredTokenIfRequired(this.source)
|
||||||
|
@ -80,6 +87,12 @@ export abstract class BaseClient {
|
||||||
// Private methods
|
// Private methods
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private getCORSProxyPath(): string {
|
||||||
|
const basePath = globalThis.location.pathname.split('/web').slice(0, 1)[0];
|
||||||
|
|
||||||
|
return `${globalThis.location.origin}${basePath || '/'}cors/`
|
||||||
|
}
|
||||||
|
|
||||||
private async refreshExpiredTokenIfRequired(source: Source): Promise<Source> {
|
private async refreshExpiredTokenIfRequired(source: Source): Promise<Source> {
|
||||||
//check if token has expired, and a refreshtoken is available
|
//check if token has expired, and a refreshtoken is available
|
||||||
// Note: source.expires_at is in seconds, Date.now() is in milliseconds.
|
// Note: source.expires_at is in seconds, Date.now() is in milliseconds.
|
||||||
|
|
|
@ -4,9 +4,11 @@ import {IResourceBundleRaw} from '../../interface';
|
||||||
import {ResourceFhir} from '../../../models/database/resource_fhir';
|
import {ResourceFhir} from '../../../models/database/resource_fhir';
|
||||||
import {NewRepositiory} from '../../../database/pouchdb_repository';
|
import {NewRepositiory} from '../../../database/pouchdb_repository';
|
||||||
import {Base64} from '../../../utils/base64';
|
import {Base64} from '../../../utils/base64';
|
||||||
|
import * as PouchDB from 'pouchdb/dist/pouchdb';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as FHIR401Client_ProcessBundle from './fixtures/FHIR401Client_ProcessBundle.json';
|
import * as FHIR401Client_ProcessBundle from './fixtures/FHIR401Client_ProcessBundle.json';
|
||||||
|
import {IDatabaseRepository} from '../../../database/interface';
|
||||||
|
|
||||||
class TestClient extends FHIR401Client {
|
class TestClient extends FHIR401Client {
|
||||||
constructor(source: Source) {
|
constructor(source: Source) {
|
||||||
|
@ -59,23 +61,36 @@ describe('FHIR401Client', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('SyncAll', () => {
|
describe('SyncAll', () => {
|
||||||
|
let repository: IDatabaseRepository;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
repository = NewRepositiory(null, null, new PouchDB("FHIR401Client-testing"));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
if(repository){
|
||||||
|
const db = await repository.GetDB()
|
||||||
|
db.destroy() //wipe the db.
|
||||||
|
}
|
||||||
|
})
|
||||||
it('should correctly add resources to the database', async () => {
|
it('should correctly add resources to the database', async () => {
|
||||||
//setup
|
//setup
|
||||||
let response = new Response(JSON.stringify(FHIR401Client_ProcessBundle));
|
let response = new Response(JSON.stringify(FHIR401Client_ProcessBundle));
|
||||||
Object.defineProperty(response, "url", { value: `${client.source.api_endpoint_base_url}/Patient/${client.source.patient}/$everything`});
|
Object.defineProperty(response, "url", { value: `${client.source.api_endpoint_base_url}/Patient/${client.source.patient}/$everything`});
|
||||||
spyOn(window, "fetch").and.returnValue(Promise.resolve(response));
|
spyOn(window, "fetch").and.returnValue(Promise.resolve(response));
|
||||||
const db = NewRepositiory("fastentest")
|
|
||||||
|
|
||||||
//test
|
//test
|
||||||
const resp = await client.SyncAll(db)
|
const resp = await client.SyncAll(repository)
|
||||||
const firstResourceFhir = resp[0]
|
const firstResourceFhir = resp[0]
|
||||||
const resourceIdParts = firstResourceFhir.split(":")
|
const resourceIdParts = firstResourceFhir.split(":")
|
||||||
|
|
||||||
//expect
|
//expect
|
||||||
expect(resp.length).toEqual(206);
|
expect(resp.totalResources).toEqual(206);
|
||||||
|
expect(resp.updatedResources).toEqual([]);
|
||||||
expect(firstResourceFhir).toEqual('resource_fhir:c291cmNlOmFldG5hOjEyMzQ1:Patient:c088b7af-fc41-43cc-ab80-4a9ab8d47cd9');
|
expect(firstResourceFhir).toEqual('resource_fhir:c291cmNlOmFldG5hOjEyMzQ1:Patient:c088b7af-fc41-43cc-ab80-4a9ab8d47cd9');
|
||||||
expect(Base64.Decode(resourceIdParts[1])).toEqual("source:aetna:12345");
|
expect(Base64.Decode(resourceIdParts[1])).toEqual("source:aetna:12345");
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {BaseClient} from './base_client';
|
||||||
import {Source} from '../../../models/database/source';
|
import {Source} from '../../../models/database/source';
|
||||||
import {IDatabaseRepository} from '../../../database/interface';
|
import {IDatabaseRepository} from '../../../database/interface';
|
||||||
import {ResourceFhir} from '../../../models/database/resource_fhir';
|
import {ResourceFhir} from '../../../models/database/resource_fhir';
|
||||||
|
import {UpsertSummary} from '../../../models/fasten/upsert-summary';
|
||||||
|
|
||||||
export class FHIR401Client extends BaseClient implements IClient {
|
export class FHIR401Client extends BaseClient implements IClient {
|
||||||
|
|
||||||
|
@ -18,13 +19,13 @@ export class FHIR401Client extends BaseClient implements IClient {
|
||||||
* @param db
|
* @param db
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
public async SyncAll(db: IDatabaseRepository): Promise<string[]> {
|
public async SyncAll(db: IDatabaseRepository): Promise<UpsertSummary> {
|
||||||
const bundle = await this.GetPatientBundle(this.source.patient)
|
const bundle = await this.GetPatientBundle(this.source.patient)
|
||||||
|
|
||||||
const wrappedResourceModels = await this.ProcessBundle(bundle)
|
const wrappedResourceModels = await this.ProcessBundle(bundle)
|
||||||
//todo, create the resources in dependency order
|
//todo, create the resources in dependency order
|
||||||
|
|
||||||
return db.CreateResources(wrappedResourceModels)
|
return db.UpsertResources(wrappedResourceModels)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ export class FHIR401Client extends BaseClient implements IClient {
|
||||||
* @param resourceNames
|
* @param resourceNames
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
public async SyncAllByResourceName(db: IDatabaseRepository, resourceNames: string[]): Promise<string[]>{
|
public async SyncAllByResourceName(db: IDatabaseRepository, resourceNames: string[]): Promise<UpsertSummary>{
|
||||||
//Store the Patient
|
//Store the Patient
|
||||||
const patientResource = await this.GetPatient(this.source.patient)
|
const patientResource = await this.GetPatient(this.source.patient)
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ export class FHIR401Client extends BaseClient implements IClient {
|
||||||
patientResourceFhir.source_resource_id = patientResource.id
|
patientResourceFhir.source_resource_id = patientResource.id
|
||||||
patientResourceFhir.resource_raw = patientResource
|
patientResourceFhir.resource_raw = patientResource
|
||||||
|
|
||||||
await db.CreateResource(patientResourceFhir)
|
const upsertSummary = await db.UpsertResource(patientResourceFhir)
|
||||||
|
|
||||||
//error map storage.
|
//error map storage.
|
||||||
let syncErrors = {}
|
let syncErrors = {}
|
||||||
|
@ -56,7 +57,9 @@ export class FHIR401Client extends BaseClient implements IClient {
|
||||||
try {
|
try {
|
||||||
let bundle = await this.GetResourceBundlePaginated(`${resourceType}?patient=${this.source.patient}`)
|
let bundle = await this.GetResourceBundlePaginated(`${resourceType}?patient=${this.source.patient}`)
|
||||||
let wrappedResourceModels = await this.ProcessBundle(bundle)
|
let wrappedResourceModels = await this.ProcessBundle(bundle)
|
||||||
await db.CreateResources(wrappedResourceModels)
|
let resourceUpsertSummary = await db.UpsertResources(wrappedResourceModels)
|
||||||
|
upsertSummary.updatedResources = upsertSummary.updatedResources.concat(resourceUpsertSummary.updatedResources)
|
||||||
|
upsertSummary.totalResources += resourceUpsertSummary.totalResources
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error(`An error occurred while processing ${resourceType} bundle ${this.source.patient}`)
|
console.error(`An error occurred while processing ${resourceType} bundle ${this.source.patient}`)
|
||||||
|
@ -66,7 +69,7 @@ export class FHIR401Client extends BaseClient implements IClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: correctly return newly inserted documents
|
//TODO: correctly return newly inserted documents
|
||||||
return []
|
return upsertSummary
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -75,7 +78,7 @@ export class FHIR401Client extends BaseClient implements IClient {
|
||||||
* @param bundleFile
|
* @param bundleFile
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
public async SyncAllFromBundleFile(db: IDatabaseRepository, bundleFile: any): Promise<any> {
|
public async SyncAllFromBundleFile(db: IDatabaseRepository, bundleFile: any): Promise<UpsertSummary> {
|
||||||
return Promise.reject(new Error("not implemented"));
|
return Promise.reject(new Error("not implemented"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {IClient} from '../interface';
|
||||||
import {FHIR401Client} from './base/fhir401_r4_client';
|
import {FHIR401Client} from './base/fhir401_r4_client';
|
||||||
import {Source} from '../../models/database/source';
|
import {Source} from '../../models/database/source';
|
||||||
import {IDatabaseRepository} from '../../database/interface';
|
import {IDatabaseRepository} from '../../database/interface';
|
||||||
|
import {UpsertSummary} from '../../models/fasten/upsert-summary';
|
||||||
|
|
||||||
export class BlueButtonClient extends FHIR401Client implements IClient {
|
export class BlueButtonClient extends FHIR401Client implements IClient {
|
||||||
constructor(source: Source) {
|
constructor(source: Source) {
|
||||||
|
@ -13,7 +14,7 @@ export class BlueButtonClient extends FHIR401Client implements IClient {
|
||||||
* @param db
|
* @param db
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
async SyncAll(db: IDatabaseRepository): Promise<string[]> {
|
async SyncAll(db: IDatabaseRepository): Promise<UpsertSummary> {
|
||||||
const supportedResources: string[] = [
|
const supportedResources: string[] = [
|
||||||
"ExplanationOfBenefit",
|
"ExplanationOfBenefit",
|
||||||
"Coverage",
|
"Coverage",
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {IClient} from '../interface';
|
||||||
import {FHIR401Client} from './base/fhir401_r4_client';
|
import {FHIR401Client} from './base/fhir401_r4_client';
|
||||||
import {Source} from '../../models/database/source';
|
import {Source} from '../../models/database/source';
|
||||||
import {IDatabaseRepository} from '../../database/interface';
|
import {IDatabaseRepository} from '../../database/interface';
|
||||||
|
import {UpsertSummary} from '../../models/fasten/upsert-summary';
|
||||||
|
|
||||||
export class CernerClient extends FHIR401Client implements IClient {
|
export class CernerClient extends FHIR401Client implements IClient {
|
||||||
constructor(source: Source) {
|
constructor(source: Source) {
|
||||||
|
@ -15,7 +16,7 @@ export class CernerClient extends FHIR401Client implements IClient {
|
||||||
* @param db
|
* @param db
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
async SyncAll(db: IDatabaseRepository): Promise<string[]> {
|
async SyncAll(db: IDatabaseRepository): Promise<UpsertSummary> {
|
||||||
const supportedResources: string[] = [
|
const supportedResources: string[] = [
|
||||||
"AllergyIntolerance",
|
"AllergyIntolerance",
|
||||||
"CarePlan",
|
"CarePlan",
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {IClient} from '../interface';
|
||||||
import {FHIR401Client} from './base/fhir401_r4_client';
|
import {FHIR401Client} from './base/fhir401_r4_client';
|
||||||
import {Source} from '../../models/database/source';
|
import {Source} from '../../models/database/source';
|
||||||
import {IDatabaseRepository} from '../../database/interface';
|
import {IDatabaseRepository} from '../../database/interface';
|
||||||
|
import {UpsertSummary} from '../../models/fasten/upsert-summary';
|
||||||
|
|
||||||
export class EpicClient extends FHIR401Client implements IClient {
|
export class EpicClient extends FHIR401Client implements IClient {
|
||||||
constructor(source: Source) {
|
constructor(source: Source) {
|
||||||
|
@ -15,7 +16,7 @@ export class EpicClient extends FHIR401Client implements IClient {
|
||||||
* @param db
|
* @param db
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
async SyncAll(db: IDatabaseRepository): Promise<string[]> {
|
async SyncAll(db: IDatabaseRepository): Promise<UpsertSummary> {
|
||||||
const supportedResources: string[] = [
|
const supportedResources: string[] = [
|
||||||
"AllergyIntolerance",
|
"AllergyIntolerance",
|
||||||
"CarePlan",
|
"CarePlan",
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {IClient} from '../interface';
|
||||||
import {FHIR401Client} from './base/fhir401_r4_client';
|
import {FHIR401Client} from './base/fhir401_r4_client';
|
||||||
import {Source} from '../../models/database/source';
|
import {Source} from '../../models/database/source';
|
||||||
import {IDatabaseRepository} from '../../database/interface';
|
import {IDatabaseRepository} from '../../database/interface';
|
||||||
|
import {UpsertSummary} from '../../models/fasten/upsert-summary';
|
||||||
|
|
||||||
export class HealthITClient extends FHIR401Client implements IClient {
|
export class HealthITClient extends FHIR401Client implements IClient {
|
||||||
constructor(source: Source) {
|
constructor(source: Source) {
|
||||||
|
@ -15,7 +16,7 @@ export class HealthITClient extends FHIR401Client implements IClient {
|
||||||
* @param db
|
* @param db
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
async SyncAll(db: IDatabaseRepository): Promise<string[]> {
|
async SyncAll(db: IDatabaseRepository): Promise<UpsertSummary> {
|
||||||
const supportedResources: string[] = [
|
const supportedResources: string[] = [
|
||||||
"AllergyIntolerance",
|
"AllergyIntolerance",
|
||||||
"CarePlan",
|
"CarePlan",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {IDatabaseRepository} from '../database/interface';
|
import {IDatabaseRepository} from '../database/interface';
|
||||||
|
import {UpsertSummary} from '../models/fasten/upsert-summary';
|
||||||
|
|
||||||
export interface IClient {
|
export interface IClient {
|
||||||
fhirVersion: string
|
fhirVersion: string
|
||||||
|
@ -10,13 +11,13 @@ export interface IClient {
|
||||||
* @param db
|
* @param db
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
SyncAll(db: IDatabaseRepository): Promise<any>
|
SyncAll(db: IDatabaseRepository): Promise<UpsertSummary>
|
||||||
|
|
||||||
|
|
||||||
SyncAllByResourceName(db: IDatabaseRepository, resourceNames: string[]): Promise<string[]>
|
SyncAllByResourceName(db: IDatabaseRepository, resourceNames: string[]): Promise<UpsertSummary>
|
||||||
|
|
||||||
//Manual client ONLY functions
|
//Manual client ONLY functions
|
||||||
SyncAllFromBundleFile(db: IDatabaseRepository, bundleFile: any): Promise<any>
|
SyncAllFromBundleFile(db: IDatabaseRepository, bundleFile: any): Promise<UpsertSummary>
|
||||||
}
|
}
|
||||||
|
|
||||||
//This is the "raw" Fhir resource
|
//This is the "raw" Fhir resource
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {ResourceFhir} from '../models/database/resource_fhir';
|
||||||
import {SourceSummary} from '../models/fasten/source-summary';
|
import {SourceSummary} from '../models/fasten/source-summary';
|
||||||
import {Summary} from '../models/fasten/summary';
|
import {Summary} from '../models/fasten/summary';
|
||||||
import {User} from '../models/fasten/user';
|
import {User} from '../models/fasten/user';
|
||||||
|
import {UpsertSummary} from '../models/fasten/upsert-summary';
|
||||||
// import {SourceSummary} from '../../app/models/fasten/source-summary';
|
// import {SourceSummary} from '../../app/models/fasten/source-summary';
|
||||||
|
|
||||||
export interface IDatabaseDocument {
|
export interface IDatabaseDocument {
|
||||||
|
@ -28,7 +29,7 @@ export interface IDatabaseRepository {
|
||||||
// GetUserByEmail(context.Context, string) (*models.User, error)
|
// GetUserByEmail(context.Context, string) (*models.User, error)
|
||||||
// GetCurrentUser(context.Context) *models.User
|
// GetCurrentUser(context.Context) *models.User
|
||||||
|
|
||||||
CreateSource(source: Source): Promise<string>
|
UpsertSource(source: Source): Promise<UpsertSummary>
|
||||||
GetSource(source_id: string): Promise<Source>
|
GetSource(source_id: string): Promise<Source>
|
||||||
DeleteSource(source_id: string): Promise<boolean>
|
DeleteSource(source_id: string): Promise<boolean>
|
||||||
GetSourceSummary(source_id: string): Promise<SourceSummary>
|
GetSourceSummary(source_id: string): Promise<SourceSummary>
|
||||||
|
@ -40,8 +41,8 @@ export interface IDatabaseRepository {
|
||||||
// GetResourceBySourceId(context.Context, string, string) (*models.ResourceFhir, error)
|
// GetResourceBySourceId(context.Context, string, string) (*models.ResourceFhir, error)
|
||||||
// ListResources(context.Context, models.ListResourceQueryOptions) ([]models.ResourceFhir, error)
|
// ListResources(context.Context, models.ListResourceQueryOptions) ([]models.ResourceFhir, error)
|
||||||
// GetPatientForSources(ctx context.Context) ([]models.ResourceFhir, error)
|
// GetPatientForSources(ctx context.Context) ([]models.ResourceFhir, error)
|
||||||
CreateResource(resource: ResourceFhir): Promise<string>
|
UpsertResource(resource: ResourceFhir): Promise<UpsertSummary>
|
||||||
CreateResources(resources: ResourceFhir[]): Promise<string[]>
|
UpsertResources(resources: ResourceFhir[]): Promise<UpsertSummary>
|
||||||
GetResource(resource_id: string): Promise<ResourceFhir>
|
GetResource(resource_id: string): Promise<ResourceFhir>
|
||||||
GetResources(): Promise<IDatabasePaginatedResponse>
|
GetResources(): Promise<IDatabasePaginatedResponse>
|
||||||
GetResourcesForSource(source_id: string, source_resource_type?: string): Promise<IDatabasePaginatedResponse>
|
GetResourcesForSource(source_id: string, source_resource_type?: string): Promise<IDatabasePaginatedResponse>
|
||||||
|
|
|
@ -3,17 +3,19 @@ import {NewRepositiory} from './pouchdb_repository';
|
||||||
import {SourceType} from '../models/database/source_types';
|
import {SourceType} from '../models/database/source_types';
|
||||||
import {Source} from '../models/database/source';
|
import {Source} from '../models/database/source';
|
||||||
import {DocType} from './constants';
|
import {DocType} from './constants';
|
||||||
|
import * as PouchDB from 'pouchdb/dist/pouchdb';
|
||||||
|
|
||||||
describe('PouchdbRepository', () => {
|
describe('PouchdbRepository', () => {
|
||||||
let repository: IDatabaseRepository;
|
let repository: IDatabaseRepository;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
repository = NewRepositiory();
|
repository = NewRepositiory(null, null, new PouchDB("PouchdbRepository-testing"));
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if(repository){
|
if(repository){
|
||||||
await repository.GetDB().destroy() //wipe the db.
|
const db = await repository.GetDB()
|
||||||
|
db.destroy() //wipe the db.
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -24,24 +26,25 @@ describe('PouchdbRepository', () => {
|
||||||
|
|
||||||
describe('CreateSource', () => {
|
describe('CreateSource', () => {
|
||||||
it('should return an id', async () => {
|
it('should return an id', async () => {
|
||||||
const createdId = await repository.CreateSource(new Source({
|
const createdId = await repository.UpsertSource(new Source({
|
||||||
patient: 'patient',
|
patient: 'patient',
|
||||||
source_type: SourceType.Aetna,
|
source_type: SourceType.Aetna,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
expect(createdId).toEqual("source:aetna:patient");
|
expect(createdId.totalResources).toEqual(1);
|
||||||
|
expect(createdId.updatedResources[0]).toEqual("source:aetna:patient");
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('GetSource', () => {
|
describe('GetSource', () => {
|
||||||
it('should return an source', async () => {
|
it('should return an source', async () => {
|
||||||
const createdId = await repository.CreateSource(new Source({
|
const createdResource = await repository.UpsertSource(new Source({
|
||||||
patient: 'patient',
|
patient: 'patient',
|
||||||
source_type: SourceType.Aetna,
|
source_type: SourceType.Aetna,
|
||||||
access_token: 'hello-world',
|
access_token: 'hello-world',
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const createdSource = await repository.GetSource(createdId)
|
const createdSource = await repository.GetSource(createdResource.updatedResources[0])
|
||||||
|
|
||||||
expect(createdSource.doc_type).toEqual(DocType.Source);
|
expect(createdSource.doc_type).toEqual(DocType.Source);
|
||||||
expect(createdSource.patient).toEqual('patient');
|
expect(createdSource.patient).toEqual('patient');
|
||||||
|
@ -52,13 +55,13 @@ describe('PouchdbRepository', () => {
|
||||||
|
|
||||||
describe('DeleteSource', () => {
|
describe('DeleteSource', () => {
|
||||||
it('should delete a source', async () => {
|
it('should delete a source', async () => {
|
||||||
const createdId = await repository.CreateSource(new Source({
|
const createdResource = await repository.UpsertSource(new Source({
|
||||||
patient: 'patient-to-delete',
|
patient: 'patient-to-delete',
|
||||||
source_type: SourceType.Aetna,
|
source_type: SourceType.Aetna,
|
||||||
access_token: 'hello-world',
|
access_token: 'hello-world',
|
||||||
}))
|
}))
|
||||||
console.log(createdId)
|
console.log(createdResource)
|
||||||
const deletedSource = await repository.DeleteSource(createdId)
|
const deletedSource = await repository.DeleteSource(createdResource.updatedResources[0])
|
||||||
|
|
||||||
expect(deletedSource).toBeTruthy();
|
expect(deletedSource).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@ -66,19 +69,19 @@ describe('PouchdbRepository', () => {
|
||||||
|
|
||||||
describe('GetSources', () => {
|
describe('GetSources', () => {
|
||||||
it('should return a list of sources', async () => {
|
it('should return a list of sources', async () => {
|
||||||
await repository.CreateSource(new Source({
|
await repository.UpsertSource(new Source({
|
||||||
patient: 'patient1',
|
patient: 'patient1',
|
||||||
source_type: SourceType.Aetna,
|
source_type: SourceType.Aetna,
|
||||||
access_token: 'hello-world1',
|
access_token: 'hello-world1',
|
||||||
}))
|
}))
|
||||||
|
|
||||||
await repository.CreateSource(new Source({
|
await repository.UpsertSource(new Source({
|
||||||
patient: 'patient2',
|
patient: 'patient2',
|
||||||
source_type: SourceType.Aetna,
|
source_type: SourceType.Aetna,
|
||||||
access_token: 'hello-world2',
|
access_token: 'hello-world2',
|
||||||
}))
|
}))
|
||||||
|
|
||||||
await repository.CreateSource(new Source({
|
await repository.UpsertSource(new Source({
|
||||||
patient: 'patient3',
|
patient: 'patient3',
|
||||||
source_type: SourceType.Aetna,
|
source_type: SourceType.Aetna,
|
||||||
access_token: 'hello-world3',
|
access_token: 'hello-world3',
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {Base64} from '../utils/base64';
|
||||||
import * as PouchDB from 'pouchdb/dist/pouchdb';
|
import * as PouchDB from 'pouchdb/dist/pouchdb';
|
||||||
import * as PouchCrypto from 'crypto-pouch';
|
import * as PouchCrypto from 'crypto-pouch';
|
||||||
import {PouchdbUpsert} from './plugins/upsert';
|
import {PouchdbUpsert} from './plugins/upsert';
|
||||||
|
import {UpsertSummary} from '../models/fasten/upsert-summary';
|
||||||
PouchDB.plugin(PouchCrypto);
|
PouchDB.plugin(PouchCrypto);
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,8 +43,8 @@ PouchDB.plugin(PouchCrypto);
|
||||||
* Eventually this method should dyanmically dtermine the version of the repo to return from the env.
|
* Eventually this method should dyanmically dtermine the version of the repo to return from the env.
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
export function NewRepositiory(userIdentifier?: string, encryptionKey?: string): IDatabaseRepository {
|
export function NewRepositiory(userIdentifier?: string, encryptionKey?: string, localPouchDb?: PouchDB.Database): IDatabaseRepository {
|
||||||
return new PouchdbRepository(userIdentifier, encryptionKey)
|
return new PouchdbRepository(userIdentifier, encryptionKey, localPouchDb)
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PouchdbRepository implements IDatabaseRepository {
|
export class PouchdbRepository implements IDatabaseRepository {
|
||||||
|
@ -60,7 +61,7 @@ export class PouchdbRepository implements IDatabaseRepository {
|
||||||
* @param userIdentifier
|
* @param userIdentifier
|
||||||
* @param encryptionKey
|
* @param encryptionKey
|
||||||
*/
|
*/
|
||||||
constructor(userIdentifier?: string, encryptionKey?: string) {
|
constructor(userIdentifier?: string, encryptionKey?: string, localPouchDb?: PouchDB.Database) {
|
||||||
this.remotePouchEndpoint = `${globalThis.location.protocol}//${globalThis.location.host}${this.getBasePath()}/database`
|
this.remotePouchEndpoint = `${globalThis.location.protocol}//${globalThis.location.host}${this.getBasePath()}/database`
|
||||||
|
|
||||||
//setup PouchDB Plugins
|
//setup PouchDB Plugins
|
||||||
|
@ -71,6 +72,10 @@ export class PouchdbRepository implements IDatabaseRepository {
|
||||||
this.encryptionKey = encryptionKey
|
this.encryptionKey = encryptionKey
|
||||||
// this.enableSync(userIdentifier)
|
// this.enableSync(userIdentifier)
|
||||||
}
|
}
|
||||||
|
if(localPouchDb){
|
||||||
|
console.warn("using local pouchdb, this should only be used for testing")
|
||||||
|
this.pouchDb = localPouchDb
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,7 +101,7 @@ export class PouchdbRepository implements IDatabaseRepository {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Source
|
// Source
|
||||||
|
|
||||||
public async CreateSource(source: Source): Promise<string> {
|
public async UpsertSource(source: Source): Promise<UpsertSummary> {
|
||||||
return this.upsertDocument(source);
|
return this.upsertDocument(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,11 +161,11 @@ export class PouchdbRepository implements IDatabaseRepository {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Resource
|
// Resource
|
||||||
|
|
||||||
public async CreateResource(resource: ResourceFhir): Promise<string> {
|
public async UpsertResource(resource: ResourceFhir): Promise<UpsertSummary> {
|
||||||
return this.upsertDocument(resource);
|
return this.upsertDocument(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async CreateResources(resources: ResourceFhir[]): Promise<string[]> {
|
public async UpsertResources(resources: ResourceFhir[]): Promise<UpsertSummary> {
|
||||||
return this.upsertBulk(resources);
|
return this.upsertBulk(resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,7 +233,7 @@ export class PouchdbRepository implements IDatabaseRepository {
|
||||||
|
|
||||||
|
|
||||||
// update/insert a new document. Returns a promise of the generated id.
|
// update/insert a new document. Returns a promise of the generated id.
|
||||||
protected upsertDocument(newDoc: IDatabaseDocument) : Promise<string> {
|
protected upsertDocument(newDoc: IDatabaseDocument) : Promise<UpsertSummary> {
|
||||||
// make sure we always "populate" the ID for every document before submitting
|
// make sure we always "populate" the ID for every document before submitting
|
||||||
newDoc.populateId()
|
newDoc.populateId()
|
||||||
|
|
||||||
|
@ -285,22 +290,29 @@ export class PouchdbRepository implements IDatabaseRepository {
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
.then(( result ): string => {
|
.then(( result ): UpsertSummary => {
|
||||||
return( result.id );
|
// // success, res is {rev: '1-xxx', updated: true, id: 'myDocId'}
|
||||||
|
const updateSummary = new UpsertSummary()
|
||||||
|
updateSummary.totalResources = 1
|
||||||
|
|
||||||
|
if(result.updated){
|
||||||
|
updateSummary.updatedResources = [result.id]
|
||||||
}
|
}
|
||||||
);
|
return updateSummary;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected upsertBulk(docs: IDatabaseDocument[]): Promise<string[]> {
|
protected upsertBulk(docs: IDatabaseDocument[]): Promise<UpsertSummary> {
|
||||||
return this.GetDB()
|
return Promise.all(docs.map((doc) => {
|
||||||
.then((db) => {
|
doc.populateId();
|
||||||
|
return this.upsertDocument(doc)
|
||||||
return Promise.all(docs.map((doc) => {
|
})).then((results) => {
|
||||||
doc.populateId();
|
return results.reduce((prev, current ) => {
|
||||||
return this.upsertDocument(doc)
|
prev.totalResources += current.totalResources
|
||||||
}))
|
prev.updatedResources = prev.updatedResources.concat(current.updatedResources)
|
||||||
|
return prev
|
||||||
})
|
}, new UpsertSummary())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getDocument(id: string): Promise<any> {
|
protected getDocument(id: string): Promise<any> {
|
||||||
|
@ -333,44 +345,6 @@ export class PouchdbRepository implements IDatabaseRepository {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//DEPRECATED
|
|
||||||
/**
|
|
||||||
* create multiple documents, returns a list of generated ids
|
|
||||||
* @deprecated
|
|
||||||
* @param docs
|
|
||||||
* @protected
|
|
||||||
*/
|
|
||||||
protected createBulk(docs: IDatabaseDocument[]): Promise<string[]> {
|
|
||||||
return this.GetDB()
|
|
||||||
.then((db) => {
|
|
||||||
return db.bulkDocs(docs.map((doc) => { doc.populateId(); return doc }))
|
|
||||||
})
|
|
||||||
.then((results): string[] => {
|
|
||||||
return results.map((result) => result.id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create a new document. Returns a promise of the generated id.
|
|
||||||
* @deprecated
|
|
||||||
* @param doc
|
|
||||||
* @protected
|
|
||||||
*/
|
|
||||||
protected createDocument(doc: IDatabaseDocument) : Promise<string> {
|
|
||||||
// make sure we always "populate" the ID for every document before submitting
|
|
||||||
doc.populateId()
|
|
||||||
|
|
||||||
// NOTE: All friends are given the key-prefix of "friend:". This way, when we go
|
|
||||||
// to query for friends, we can limit the scope to keys with in this key-space.
|
|
||||||
|
|
||||||
return this.GetDB()
|
|
||||||
.then((db) => db.put(doc))
|
|
||||||
.then(( result ): string => {
|
|
||||||
return( result.id );
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Sync private/protected methods
|
// Sync private/protected methods
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import {Source} from '../database/source';
|
||||||
|
import {ResourceFhir} from '../database/resource_fhir';
|
||||||
|
import {ResourceTypeCounts} from './source-summary';
|
||||||
|
|
||||||
|
export class UpsertSummary {
|
||||||
|
updatedResources: string[] = []
|
||||||
|
totalResources: number = 0
|
||||||
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"skipLibCheck": true, //without this the "dom" and the "webworker" libs conflict
|
||||||
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"outDir": "./out-tsc/spec",
|
"outDir": "./out-tsc/spec",
|
||||||
"types": [
|
"types": [
|
||||||
"jasmine",
|
"jasmine",
|
||||||
"node"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
Loading…
Reference in New Issue