Skip to content

Commit

Permalink
Timeline based Manual Entry form (#330)
Browse files Browse the repository at this point in the history
  • Loading branch information
AnalogJ authored Dec 4, 2023
1 parent 7a77dcd commit d23af01
Show file tree
Hide file tree
Showing 280 changed files with 5,335 additions and 2,827 deletions.
150 changes: 124 additions & 26 deletions backend/pkg/database/gorm_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
"strings"
"time"

Expand Down Expand Up @@ -39,6 +40,7 @@ func (gr *GormRepository) Close() error {
// User
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// <editor-fold desc="User">
func (gr *GormRepository) CreateUser(ctx context.Context, user *models.User) error {
if err := user.HashPassword(user.Password); err != nil {
return err
Expand All @@ -53,6 +55,17 @@ func (gr *GormRepository) CreateUser(ctx context.Context, user *models.User) err
if err != nil {
return err
}

//create Fasten source credential for this user.
fastenUserCred := models.SourceCredential{
UserID: user.ID,
SourceType: sourcePkg.SourceTypeFasten,
}
fastenUserCredResp := gr.GormClient.Create(&fastenUserCred)
if fastenUserCredResp.Error != nil {
return fastenUserCredResp.Error
}

return nil
}
func (gr *GormRepository) GetUserByUsername(ctx context.Context, username string) (*models.User, error) {
Expand Down Expand Up @@ -94,10 +107,13 @@ func (gr *GormRepository) GetCurrentUser(ctx context.Context) (*models.User, err
return &currentUser, nil
}

//</editor-fold>

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Glossary
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// <editor-fold desc="Glossary">
func (gr *GormRepository) CreateGlossaryEntry(ctx context.Context, glossaryEntry *models.Glossary) error {
record := gr.GormClient.WithContext(ctx).Create(glossaryEntry)
if record.Error != nil {
Expand All @@ -114,6 +130,8 @@ func (gr *GormRepository) GetGlossaryEntry(ctx context.Context, code string, cod
return &foundGlossaryEntry, result.Error
}

//</editor-fold>

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Summary
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -180,6 +198,8 @@ func (gr *GormRepository) GetSummary(ctx context.Context) (*models.Summary, erro
// Resource
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// <editor-fold desc="Resource">

// This function will create a new resource if it does not exist, or update an existing resource if it does exist.
// It will also create associations between fhir resources
// This function is called directly by fasten-sources
Expand Down Expand Up @@ -210,30 +230,54 @@ func (gr *GormRepository) UpsertRawResource(ctx context.Context, sourceCredentia
//note: these associations are not reciprocal, (i.e. if Procedure references Location, Location may not reference Procedure)
if rawResource.ReferencedResources != nil && len(rawResource.ReferencedResources) > 0 {
for _, referencedResource := range rawResource.ReferencedResources {
parts := strings.Split(referencedResource, "/")
if len(parts) != 2 {
continue
}

relatedResource := &models.ResourceBase{
OriginBase: models.OriginBase{
SourceID: source.ID,
SourceResourceType: parts[0],
SourceResourceID: parts[1],
},
RelatedResource: nil,
}
err := gr.AddResourceAssociation(
ctx,
source,
wrappedResourceModel.SourceResourceType,
wrappedResourceModel.SourceResourceID,
source,
relatedResource.SourceResourceType,
relatedResource.SourceResourceID,
)
if err != nil {
return false, err
var relatedResource *models.ResourceBase

if strings.HasPrefix(referencedResource, sourcePkg.FASTENHEALTH_URN_PREFIX) {
gr.Logger.Infof("parsing external urn:fastenhealth-fhir reference: %v", referencedResource)

targetSourceId, targetResourceType, targetResourceId, err := sourcePkg.ParseReferenceUri(&referencedResource)
if err != nil {
gr.Logger.Warnf("could not parse urn:fastenhealth-fhir reference: %v", referencedResource)
continue
}
err = gr.UpsertRawResourceAssociation(
ctx,
source.ID.String(),
wrappedResourceModel.SourceResourceType,
wrappedResourceModel.SourceResourceID,
targetSourceId,
targetResourceType,
targetResourceId,
)
if err != nil {
return false, err
}
} else {
parts := strings.Split(referencedResource, "/")
if len(parts) != 2 {
continue
}
relatedResource = &models.ResourceBase{
OriginBase: models.OriginBase{
SourceID: source.ID,
SourceResourceType: parts[0],
SourceResourceID: parts[1],
},
RelatedResource: nil,
}
err := gr.AddResourceAssociation(
ctx,
source,
wrappedResourceModel.SourceResourceType,
wrappedResourceModel.SourceResourceID,
source,
relatedResource.SourceResourceType,
relatedResource.SourceResourceID,
)
if err != nil {
return false, err
}
}
}
}
Expand All @@ -242,6 +286,44 @@ func (gr *GormRepository) UpsertRawResource(ctx context.Context, sourceCredentia

}

func (gr *GormRepository) UpsertRawResourceAssociation(
ctx context.Context,
sourceId string,
sourceResourceType string,
sourceResourceId string,
targetSourceId string,
targetResourceType string,
targetResourceId string,
) error {

if sourceId == targetSourceId && sourceResourceType == targetResourceType && sourceResourceId == targetResourceId {
gr.Logger.Warnf("cannot create self-referential association, ignoring")
return nil
}
var sourceCredential *models.SourceCredential
var targetSourceCredential *models.SourceCredential
var err error
if sourceId == targetSourceId {
sourceCredential, err = gr.GetSource(ctx, sourceId)
if err != nil {
return err
}
targetSourceCredential = sourceCredential
} else {
sourceCredential, err = gr.GetSource(ctx, sourceId)
if err != nil {
return err
}
targetSourceCredential, err = gr.GetSource(ctx, targetSourceId)
if err != nil {
return err
}
}

//SECURITY: sourceCredential and targetSourceCredential are guaranteed to be owned by the same user, and will be confirmed within the addAssociation function
return gr.AddResourceAssociation(ctx, sourceCredential, sourceResourceType, sourceResourceId, targetSourceCredential, targetResourceType, targetResourceId)
}

// UpsertResource
// this method will upsert a resource, however it will not create associations.
// UPSERT operation
Expand Down Expand Up @@ -448,10 +530,14 @@ func (gr *GormRepository) GetPatientForSources(ctx context.Context) ([]models.Re
return wrappedResourceModels, results.Error
}

//</editor-fold>

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Resource Associations
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//<editor-fold desc="Resource Associations">

// verifyAssociationPermission ensure that the sources are "owned" by the same user, and that the user is the current user
func (gr *GormRepository) verifyAssociationPermission(ctx context.Context, sourceUserID uuid.UUID, relatedSourceUserID uuid.UUID) error {
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
Expand Down Expand Up @@ -542,11 +628,14 @@ func (gr *GormRepository) FindResourceAssociationsByTypeAndId(ctx context.Contex
ResourceBaseSourceID: source.ID,
ResourceBaseSourceResourceType: resourceType,
ResourceBaseSourceResourceID: resourceId,
RelatedResourceUserID: currentUser.ID,
}).
Find(&relatedResources)
return relatedResources, result.Error
}

//</editor-fold>

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Resource Composition (Grouping)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand All @@ -565,6 +654,8 @@ func (gr *GormRepository) FindResourceAssociationsByTypeAndId(ctx context.Contex
// - add AddResourceAssociation for all resources linked to the Composition resource
// - store the Composition resource
// TODO: determine if we should be using a List Resource instead of a Composition resource
//
// Deprecated: This method has been deprecated. It has been replaced in favor of Fasten SourceCredential & associations
func (gr *GormRepository) AddResourceComposition(ctx context.Context, compositionTitle string, resources []*models.ResourceBase) error {
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
if currentUserErr != nil {
Expand Down Expand Up @@ -718,6 +809,8 @@ func (gr *GormRepository) AddResourceComposition(ctx context.Context, compositio
// SourceCredential
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//<editor-fold desc="SourceCredential">

func (gr *GormRepository) CreateSource(ctx context.Context, sourceCreds *models.SourceCredential) error {
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
if currentUserErr != nil {
Expand Down Expand Up @@ -846,10 +939,10 @@ func (gr *GormRepository) GetSourceSummary(ctx context.Context, sourceId string)
Table(patientTableName).
First(&wrappedPatientResourceModel)

if patientResults.Error != nil {
return nil, patientResults.Error
//some sources may not have a patient resource (including the Fasten source)
if patientResults.Error == nil {
sourceSummary.Patient = &wrappedPatientResourceModel
}
sourceSummary.Patient = &wrappedPatientResourceModel

return sourceSummary, nil
}
Expand Down Expand Up @@ -925,10 +1018,13 @@ func (gr *GormRepository) DeleteSource(ctx context.Context, sourceId string) (in
return rowsEffected, results.Error
}

//</editor-fold>

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Background Job
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// <editor-fold desc="Background Job & Checkpoints">
func (gr *GormRepository) CreateBackgroundJob(ctx context.Context, backgroundJob *models.BackgroundJob) error {
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
if currentUserErr != nil {
Expand Down Expand Up @@ -1117,6 +1213,8 @@ func (gr *GormRepository) CancelAllLockedBackgroundJobsAndFail() error {

}

//</editor-fold>

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
30 changes: 29 additions & 1 deletion backend/pkg/database/gorm_repository_migrations.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package database

import (
"context"
"fmt"
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
databaseModel "github.com/fastenhealth/fasten-onprem/backend/pkg/models/database"
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
Expand All @@ -14,7 +17,7 @@ func (gr *GormRepository) Migrate() error {
gormMigrateOptions := gormigrate.DefaultOptions
gormMigrateOptions.UseTransaction = true

//use echo $(date '+%Y%m%d%H%M%S') to generate new ID's
//use "echo $(date '+%Y%m%d%H%M%S')" to generate new ID's
m := gormigrate.New(gr.GormClient, gormMigrateOptions, []*gormigrate.Migration{
{
ID: "20231017112246", // base database models //TODO: figure out how to version these correctly (SourceCredential is complicated)
Expand All @@ -37,6 +40,31 @@ func (gr *GormRepository) Migrate() error {
return databaseModel.Migrate(tx)
},
},
{
ID: "20231201122541", // Adding Fasten Source Credential for each user
Migrate: func(tx *gorm.DB) error {

users := []models.User{}
results := tx.Find(&users)
if results.Error != nil {
return results.Error
}
for _, user := range users {
tx.Logger.Info(context.Background(), fmt.Sprintf("Creating Fasten Source Credential for user: %s", user.ID))

fastenUserCred := models.SourceCredential{
UserID: user.ID,
SourceType: sourcePkg.SourceTypeFasten,
}
fastenUserCredCreateResp := tx.Create(&fastenUserCred)
if fastenUserCredCreateResp.Error != nil {
tx.Logger.Error(context.Background(), fmt.Sprintf("An error occurred creating Fasten Source Credential for user: %s", user.ID))
return fastenUserCredCreateResp.Error
}
}
return nil
},
},
})

if err := m.Migrate(); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion backend/pkg/database/gorm_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1021,7 +1021,7 @@ func (suite *RepositoryTestSuite) TestGetSummary() {
{"count": int64(16), "resource_type": "Procedure"},
}, sourceSummary.ResourceTypeCounts)

require.Equal(suite.T(), 2, len(sourceSummary.Sources))
require.Equal(suite.T(), 3, len(sourceSummary.Sources))
require.Equal(suite.T(), 2, len(sourceSummary.Patients))
}

Expand Down
13 changes: 12 additions & 1 deletion backend/pkg/database/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type DatabaseRepository interface {
RemoveResourceAssociation(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string, relatedSource *models.SourceCredential, relatedResourceType string, relatedResourceId string) error
FindResourceAssociationsByTypeAndId(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string) ([]models.RelatedResource, error)
GetFlattenedResourceGraph(ctx context.Context, graphType pkg.ResourceGraphType, options models.ResourceGraphOptions) (map[string][]*models.ResourceBase, error)

// Deprecated:This method has been deprecated. It has been replaced in favor of Fasten SourceCredential & associations
AddResourceComposition(ctx context.Context, compositionTitle string, resources []*models.ResourceBase) error
//UpsertProfile(context.Context, *models.Profile) error
//UpsertOrganziation(context.Context, *models.Organization) error
Expand All @@ -55,6 +57,15 @@ type DatabaseRepository interface {
PopulateDefaultUserSettings(ctx context.Context, userId uuid.UUID) error

//used by fasten-sources Clients
UpsertRawResource(ctx context.Context, sourceCredentials sourcePkg.SourceCredential, rawResource sourcePkg.RawResourceFhir) (bool, error)
BackgroundJobCheckpoint(ctx context.Context, checkpointData map[string]interface{}, errorData map[string]interface{})
UpsertRawResource(ctx context.Context, sourceCredentials sourcePkg.SourceCredential, rawResource sourcePkg.RawResourceFhir) (bool, error)
UpsertRawResourceAssociation(
ctx context.Context,
sourceId string,
sourceResourceType string,
sourceResourceId string,
targetSourceId string,
targetResourceType string,
targetResourceId string,
) error
}
3 changes: 3 additions & 0 deletions backend/pkg/models/source_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ type SourceCredential struct {
func (s *SourceCredential) GetSourceType() sourcesPkg.SourceType {
return s.SourceType
}
func (s *SourceCredential) GetSourceId() string {
return s.ID.String()
}

func (s *SourceCredential) GetClientId() string {
return s.ClientId
Expand Down
Loading

0 comments on commit d23af01

Please sign in to comment.