support logica service.

update dashboard.
moved RAW API endpoint outside of the secure block.
This commit is contained in:
Jason Kulatunga 2022-09-18 00:36:53 -07:00
parent 02dcbcc507
commit cc485a0615
13 changed files with 127 additions and 27 deletions

View File

@ -13,4 +13,5 @@ const (
SourceTypeHumana SourceType = "humana"
SourceTypeKaiser SourceType = "kaiser"
SourceTypeUnitedHealthcare SourceType = "unitedhealthcare"
SourceTypeLogica SourceType = "logica"
)

View File

@ -19,6 +19,7 @@ type DatabaseRepository interface {
GetResource(context.Context, string) (*models.ResourceFhir, error)
GetResourceBySourceId(context.Context, string, string) (*models.ResourceFhir, error)
ListResources(context.Context, models.ListResourceQueryOptions) ([]models.ResourceFhir, error)
GetPatientForSources(ctx context.Context) ([]models.ResourceFhir, error)
//UpsertProfile(context.Context, *models.Profile) error
//UpsertOrganziation(context.Context, *models.Organization) error

View File

@ -138,9 +138,16 @@ func (sr *sqliteRepository) GetSummary(ctx context.Context) (*models.Summary, er
return nil, err
}
// we want the main Patient for each source
patients, err := sr.GetPatientForSources(ctx)
if err != nil {
return nil, err
}
summary := &models.Summary{
Sources: sources,
ResourceTypeCounts: resourceCountResults,
Patients: patients,
}
return summary, nil
@ -237,6 +244,29 @@ func (sr *sqliteRepository) GetResource(ctx context.Context, resourceId string)
return &wrappedResourceModel, results.Error
}
// Get the patient for each source (for the current user)
func (sr *sqliteRepository) GetPatientForSources(ctx context.Context) ([]models.ResourceFhir, error) {
//SELECT * FROM resource_fhirs WHERE user_id = "" and source_resource_type = "Patient" GROUP BY source_id
//var sourceCred models.Source
//results := sr.gormClient.WithContext(ctx).
// Where(models.Source{UserID: sr.GetCurrentUser(ctx).ID, ModelBase: models.ModelBase{ID: sourceUUID}}).
// First(&sourceCred)
var wrappedResourceModels []models.ResourceFhir
results := sr.gormClient.WithContext(ctx).
Model(models.ResourceFhir{}).
Group("source_id").
Where(models.OriginBase{
UserID: sr.GetCurrentUser(ctx).ID,
SourceResourceType: "Patient",
}).
Find(&wrappedResourceModels)
return wrappedResourceModels, results.Error
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Source
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -9,6 +9,7 @@ import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/aetna"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/base"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/cigna"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/logica"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/manual"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/sirupsen/logrus"
@ -27,6 +28,8 @@ func NewClient(sourceType pkg.SourceType, ctx context.Context, appConfig config.
sourceClient, updatedSource, err = cigna.NewClient(ctx, appConfig, globalLogger, credentials, testHttpClient...)
case pkg.SourceTypeCigna:
sourceClient, updatedSource, err = cigna.NewClient(ctx, appConfig, globalLogger, credentials, testHttpClient...)
case pkg.SourceTypeLogica:
sourceClient, updatedSource, err = logica.NewClient(ctx, appConfig, globalLogger, credentials, testHttpClient...)
case pkg.SourceTypeManual:
sourceClient, updatedSource, err = manual.NewClient(ctx, appConfig, globalLogger, credentials, testHttpClient...)
default:

View File

@ -0,0 +1,42 @@
package logica
import (
"context"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/base"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/sirupsen/logrus"
"net/http"
)
type LogicaClient struct {
*base.FHIR401Client
}
func NewClient(ctx context.Context, appConfig config.Interface, globalLogger logrus.FieldLogger, source models.Source, testHttpClient ...*http.Client) (base.Client, *models.Source, error) {
baseClient, updatedSource, err := base.NewFHIR401Client(ctx, appConfig, globalLogger, source, testHttpClient...)
return LogicaClient{
baseClient,
}, updatedSource, err
}
func (c LogicaClient) SyncAll(db database.DatabaseRepository) error {
bundle, err := c.GetPatientBundle(c.Source.PatientId)
if err != nil {
return err
}
wrappedResourceModels, err := c.ProcessBundle(bundle)
//todo, create the resources in dependency order
for _, apiModel := range wrappedResourceModels {
err = db.UpsertResource(context.Background(), apiModel)
if err != nil {
return err
}
}
return nil
}

View File

@ -2,5 +2,6 @@ package models
type Summary struct {
Sources []Source `json:"sources,omitempty"`
Patients []ResourceFhir `json:"patients,omitempty"`
ResourceTypeCounts []map[string]interface{} `json:"resource_type_counts,omitempty"`
}

View File

@ -148,38 +148,34 @@ func RawRequestSource(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
sources, err := databaseRepo.GetSources(c)
if err != nil {
logger.Errorln("An error occurred while storing source credential", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
return
}
//!!!!!!INSECURE!!!!!!S
//We're setting the username to a user provided value, this is insecure, but required for calling databaseRepo fns
c.Set("AUTH_USERNAME", c.Param("username"))
var foundSource *models.Source
for _, source := range sources {
if source.SourceType == pkg.SourceType(c.Param("sourceType")) {
foundSource = &source
break
}
foundSource, err := databaseRepo.GetSource(c, c.Param("sourceId"))
if err != nil {
logger.Errorln("An error occurred while finding source credential", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}
if foundSource == nil {
logger.Errorf("Did not source credentials for %s", c.Param("sourceType"))
c.JSON(http.StatusNotFound, gin.H{"success": false})
c.JSON(http.StatusNotFound, gin.H{"success": false, "error": err.Error()})
return
}
client, updatedSource, err := hub.NewClient(pkg.SourceType(c.Param("sourceType")), c, nil, logger, *foundSource)
client, updatedSource, err := hub.NewClient(foundSource.SourceType, c, nil, logger, *foundSource)
if err != nil {
logger.Errorf("Could not initialize source client", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}
if updatedSource != nil {
err := databaseRepo.CreateSource(c, updatedSource)
if err != nil {
logger.Errorln("An error occurred while updating source credential", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}
}
@ -188,7 +184,7 @@ func RawRequestSource(c *gin.Context) {
err = client.GetRequest(strings.TrimSuffix(c.Param("path"), "/"), &resp)
if err != nil {
logger.Errorf("Error making raw request", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": resp})

View File

@ -52,12 +52,17 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
secure.GET("/source", handler.ListSource)
secure.GET("/source/:sourceId", handler.GetSource)
secure.GET("/source/:sourceId/summary", handler.GetSourceSummary)
//in debug mode, this endpoint lets us request data directly from the source api
secure.GET("/source/raw/:sourceType/*path", handler.RawRequestSource)
secure.GET("/resource/fhir", handler.ListResourceFhir) //
secure.GET("/resource/fhir/:resourceId", handler.GetResourceFhir)
}
if ae.Config.GetString("log.level") == "DEBUG" {
//in debug mode, this endpoint lets us request data directly from the source api
ae.Logger.Warningf("***INSECURE*** ***INSECURE*** DEBUG mode enables developer functionality, including unauthenticated raw api requests")
//http://localhost:9090/api/raw/test@test.com/436d7277-ad56-41ce-9823-44e353d1b3f6/Patient/smart-1288992
api.GET("/raw/:username/:sourceId/*path", handler.RawRequestSource)
}
}
}

View File

@ -1,7 +1,9 @@
import {Source} from './source';
import {ResourceTypeCounts} from './source-summary';
import {ResourceFhir} from './resource_fhir';
export class Summary {
sources: Source[]
patients: ResourceFhir[]
resource_type_counts: ResourceTypeCounts[]
}

View File

@ -94,7 +94,7 @@
<canvas baseChart [chartType]="'doughnut'" [datasets]="sessionsChartTwoData" [labels]="sessionsChartTwoLabels" [options]="sessionsChartTwoOptions" height="45"></canvas>
</div>
<div>
<label>Updated</label>
<label>Updates</label>
<h4>19</h4>
</div>
</div>
@ -154,8 +154,7 @@
<div class="media-body">
<h5>{{source.source_type}}</h5>
<p>
GeeksforGeeks is a computer science portal.
It is a best programming platform.
{{getPatientSummary(patientForSource[source.id]?.payload)}}
</p>
</div>

View File

@ -5,6 +5,7 @@ import {Source} from '../../models/fasten/source';
import {Router} from '@angular/router';
import {Summary} from '../../models/fasten/summary';
import {ResourceTypeCounts} from '../../models/fasten/source-summary';
import {ResourceFhir} from '../../models/fasten/resource_fhir';
@Component({
selector: 'app-dashboard',
@ -16,6 +17,7 @@ export class DashboardComponent implements OnInit {
sources: Source[] = []
encounterCount: number = 0
recordsCount: number = 0
patientForSource: {[name: string]: ResourceFhir} = {}
constructor(private fastenApi: FastenApiService, private router: Router) { }
@ -33,9 +35,11 @@ export class DashboardComponent implements OnInit {
this.encounterCount = resourceTypeInfo.count
}
})
})
// this.fastenApi.getResources('Patient')
summary.patients.forEach((resourceFhir) => {
this.patientForSource[resourceFhir.source_id] = resourceFhir
})
})
}
selectSource(selectedSource: Source){
@ -44,6 +48,12 @@ export class DashboardComponent implements OnInit {
});
}
getPatientSummary(patient: any) {
if(patient && patient.name && patient.name[0]){
return `${patient.name[0].family}, ${patient.name[0].given.join(' ')}`
}
return ''
}
pageViewChartData = [{
label: 'This week',

View File

@ -5,7 +5,7 @@ import {LighthouseSource} from '../../models/lighthouse/lighthouse-source';
import * as Oauth from '@panva/oauth4webapi';
import {AuthorizeClaim} from '../../models/lighthouse/authorize-claim';
import {Source} from '../../models/fasten/source';
import {getAccessTokenExpiration} from 'fhirclient/lib/lib';
import {getAccessTokenExpiration, jwtDecode} from 'fhirclient/lib/lib';
import BrowserAdapter from 'fhirclient/lib/adapters/BrowserAdapter';
import {Observable, of, throwError} from 'rxjs';
import {concatMap, delay, retryWhen} from 'rxjs/operators';
@ -34,6 +34,7 @@ export class MedicalSourcesComponent implements OnInit {
"humana": {"display": "Humana"},
"kaiser": {"display": "Kaiser"},
"unitedhealthcare": {"display": "United Healthcare"},
"logica": {"display": "Logica Sandbox"},
}
connectedSourceList = []
@ -103,7 +104,7 @@ export class MedicalSourcesComponent implements OnInit {
issuer: `${authorizationUrl.protocol}//${authorizationUrl.host}`,
authorization_endpoint: `${connectData.oauth_endpoint_base_url}/authorize`,
token_endpoint: `${connectData.oauth_endpoint_base_url}/token`,
introspect_endpoint: `${connectData.oauth_endpoint_base_url}/introspect`,
introspection_endpoint: `${connectData.oauth_endpoint_base_url}/introspect`,
}
console.log("STARTING--- Oauth.validateAuthResponse")
@ -125,6 +126,15 @@ export class MedicalSourcesComponent implements OnInit {
console.log("ENDING--- Oauth.authorizationCodeGrantRequest", payload)
//If payload.patient is not set, make sure we extract the patient ID from the id_token or make an introspection req
if(!payload.patient){
//
console.log("NO PATIENT ID present, decoding jwt to extract patient")
//const introspectionResp = await Oauth.introspectionRequest(as, client, payload.access_token)
//console.log(introspectionResp)
payload.patient = jwtDecode(payload.id_token, new BrowserAdapter())["profile"].replace(/^(Patient\/)/,'')
}
//Create FHIR Client
const sourceCredential: Source = {
source_type: sourceType,

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB