2022-08-25 19:26:29 -06:00
package database
import (
"context"
2022-09-14 22:56:32 -06:00
"encoding/json"
2022-08-25 19:26:29 -06:00
"fmt"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
2022-09-12 19:20:56 -06:00
"github.com/gin-gonic/gin"
2022-08-25 19:26:29 -06:00
"github.com/glebarez/sqlite"
2022-09-14 20:59:16 -06:00
"github.com/google/uuid"
2022-08-25 19:26:29 -06:00
"github.com/sirupsen/logrus"
"gorm.io/gorm"
"net/url"
)
func NewRepository ( appConfig config . Interface , globalLogger logrus . FieldLogger ) ( DatabaseRepository , error ) {
//backgroundContext := context.Background()
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Gorm/SQLite setup
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
globalLogger . Infof ( "Trying to connect to sqlite db: %s\n" , appConfig . GetString ( "web.database.location" ) )
// When a transaction cannot lock the database, because it is already locked by another one,
// SQLite by default throws an error: database is locked. This behavior is usually not appropriate when
// concurrent access is needed, typically when multiple processes write to the same database.
// PRAGMA busy_timeout lets you set a timeout or a handler for these events. When setting a timeout,
// SQLite will try the transaction multiple times within this timeout.
// fixes #341
// https://rsqlite.r-dbi.org/reference/sqlitesetbusyhandler
// retrying for 30000 milliseconds, 30seconds - this would be unreasonable for a distributed multi-tenant application,
// but should be fine for local usage.
pragmaStr := sqlitePragmaString ( map [ string ] string {
"busy_timeout" : "30000" ,
2022-08-27 09:17:09 -06:00
"foreign_keys" : "ON" ,
2022-08-25 19:26:29 -06:00
} )
database , err := gorm . Open ( sqlite . Open ( appConfig . GetString ( "web.database.location" ) + pragmaStr ) , & gorm . Config {
//TODO: figure out how to log database queries again.
//Logger: logger
DisableForeignKeyConstraintWhenMigrating : true ,
} )
if err != nil {
return nil , fmt . Errorf ( "Failed to connect to database! - %v" , err )
}
globalLogger . Infof ( "Successfully connected to scrutiny sqlite db: %s\n" , appConfig . GetString ( "web.database.location" ) )
2022-08-25 22:57:29 -06:00
//TODO: automigrate for now
2022-08-30 20:55:27 -06:00
err = database . AutoMigrate (
& models . User { } ,
& models . Source { } ,
2022-09-08 22:14:03 -06:00
& models . ResourceFhir { } ,
2022-08-30 20:55:27 -06:00
)
2022-08-25 22:57:29 -06:00
if err != nil {
return nil , fmt . Errorf ( "Failed to automigrate! - %v" , err )
}
2022-08-27 09:39:55 -06:00
// create/update admin user
adminUser := models . User { }
err = database . FirstOrCreate ( & adminUser , models . User { Username : "admin" } ) . Error
if err != nil {
return nil , fmt . Errorf ( "Failed to create admin user! - %v" , err )
}
2022-08-25 19:26:29 -06:00
deviceRepo := sqliteRepository {
appConfig : appConfig ,
logger : globalLogger ,
gormClient : database ,
}
return & deviceRepo , nil
}
type sqliteRepository struct {
appConfig config . Interface
logger logrus . FieldLogger
gormClient * gorm . DB
}
func ( sr * sqliteRepository ) Close ( ) error {
return nil
}
2022-09-11 21:59:13 -06:00
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// User
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func ( sr * sqliteRepository ) CreateUser ( ctx context . Context , user * models . User ) error {
if err := user . HashPassword ( user . Password ) ; err != nil {
return err
}
2022-09-24 23:18:07 -06:00
record := sr . gormClient . Create ( user )
2022-09-11 21:59:13 -06:00
if record . Error != nil {
return record . Error
}
return nil
}
func ( sr * sqliteRepository ) GetUserByEmail ( ctx context . Context , username string ) ( * models . User , error ) {
var foundUser models . User
2022-09-24 23:18:07 -06:00
result := sr . gormClient . Where ( models . User { Username : username } ) . First ( & foundUser )
2022-09-11 21:59:13 -06:00
return & foundUser , result . Error
}
2022-09-24 23:18:07 -06:00
func ( sr * sqliteRepository ) GetCurrentUser ( ctx context . Context ) * models . User {
2022-09-12 19:20:56 -06:00
ginCtx := ctx . ( * gin . Context )
2022-08-30 20:55:27 -06:00
var currentUser models . User
2022-09-24 23:18:07 -06:00
sr . gormClient . First ( & currentUser , models . User { Username : ginCtx . MustGet ( "AUTH_USERNAME" ) . ( string ) } )
2022-08-30 20:55:27 -06:00
2022-09-24 23:18:07 -06:00
return & currentUser
2022-08-30 20:55:27 -06:00
}
2022-09-17 11:14:59 -06:00
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// User
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func ( sr * sqliteRepository ) GetSummary ( ctx context . Context ) ( * models . Summary , error ) {
// we want a count of all resources for this user by type
var resourceCountResults [ ] map [ string ] interface { }
//group by resource type and return counts
// SELECT source_resource_type as resource_type, COUNT(*) as count FROM resource_fhirs WHERE source_id = "53c1e930-63af-46c9-b760-8e83cbc1abd9" GROUP BY source_resource_type;
result := sr . gormClient . WithContext ( ctx ) .
Model ( models . ResourceFhir { } ) .
Select ( "source_id, source_resource_type as resource_type, count(*) as count" ) .
Group ( "source_resource_type" ) .
Where ( models . OriginBase {
UserID : sr . GetCurrentUser ( ctx ) . ID ,
} ) .
Scan ( & resourceCountResults )
if result . Error != nil {
return nil , result . Error
}
// we want a list of all sources (when they were last updated)
sources , err := sr . GetSources ( ctx )
if err != nil {
return nil , err
}
2022-09-18 01:36:53 -06:00
// we want the main Patient for each source
patients , err := sr . GetPatientForSources ( ctx )
if err != nil {
return nil , err
}
2022-09-17 11:14:59 -06:00
summary := & models . Summary {
Sources : sources ,
ResourceTypeCounts : resourceCountResults ,
2022-09-18 01:36:53 -06:00
Patients : patients ,
2022-09-17 11:14:59 -06:00
}
return summary , nil
}
2022-09-08 23:53:54 -06:00
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Resource
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2022-09-24 23:18:07 -06:00
func ( sr * sqliteRepository ) UpsertResource ( ctx context . Context , resourceModel * models . ResourceFhir ) error {
2022-09-01 19:54:01 -06:00
sr . logger . Infof ( "insert/update (%T) %v" , resourceModel , resourceModel )
2022-09-24 23:18:07 -06:00
if sr . gormClient . Debug ( ) . WithContext ( ctx ) .
2022-08-30 22:36:40 -06:00
Where ( models . OriginBase {
2022-09-08 22:14:03 -06:00
SourceID : resourceModel . GetSourceID ( ) ,
SourceResourceID : resourceModel . GetSourceResourceID ( ) ,
SourceResourceType : resourceModel . GetSourceResourceType ( ) , //TODO: and UpdatedAt > old UpdatedAt
2022-09-24 23:18:07 -06:00
} ) . Updates ( resourceModel ) . RowsAffected == 0 {
2022-09-08 22:14:03 -06:00
sr . logger . Infof ( "resource does not exist, creating: %s %s %s" , resourceModel . GetSourceID ( ) , resourceModel . GetSourceResourceID ( ) , resourceModel . GetSourceResourceType ( ) )
2022-09-24 23:18:07 -06:00
return sr . gormClient . Debug ( ) . Create ( resourceModel ) . Error
2022-08-30 20:55:27 -06:00
}
return nil
2022-08-27 09:39:55 -06:00
}
2022-09-14 22:56:32 -06:00
func ( sr * sqliteRepository ) ListResources ( ctx context . Context , queryOptions models . ListResourceQueryOptions ) ( [ ] models . ResourceFhir , error ) {
2022-09-08 23:53:54 -06:00
queryParam := models . ResourceFhir {
OriginBase : models . OriginBase {
2022-09-14 22:56:32 -06:00
UserID : sr . GetCurrentUser ( ctx ) . ID ,
2022-09-08 23:53:54 -06:00
} ,
}
2022-09-14 22:56:32 -06:00
if len ( queryOptions . SourceResourceType ) > 0 {
queryParam . OriginBase . SourceResourceType = queryOptions . SourceResourceType
}
if len ( queryOptions . SourceID ) > 0 {
sourceUUID , err := uuid . Parse ( queryOptions . SourceID )
if err != nil {
return nil , err
}
queryParam . OriginBase . SourceID = sourceUUID
2022-09-08 23:53:54 -06:00
}
2022-09-14 22:56:32 -06:00
manifestJson , _ := json . MarshalIndent ( queryParam , "" , " " )
sr . logger . Infof ( "THE QUERY OBJECT===========> %v" , string ( manifestJson ) )
2022-09-08 23:53:54 -06:00
var wrappedResourceModels [ ] models . ResourceFhir
results := sr . gormClient . WithContext ( ctx ) .
Where ( queryParam ) .
Find ( & wrappedResourceModels )
return wrappedResourceModels , results . Error
}
2022-09-14 22:56:32 -06:00
func ( sr * sqliteRepository ) GetResourceBySourceId ( ctx context . Context , sourceResourceType string , sourceResourceId string ) ( * models . ResourceFhir , error ) {
queryParam := models . ResourceFhir {
OriginBase : models . OriginBase {
UserID : sr . GetCurrentUser ( ctx ) . ID ,
SourceResourceType : sourceResourceType ,
SourceResourceID : sourceResourceId ,
} ,
}
var wrappedResourceModel models . ResourceFhir
results := sr . gormClient . WithContext ( ctx ) .
Where ( queryParam ) .
First ( & wrappedResourceModel )
return & wrappedResourceModel , results . Error
}
func ( sr * sqliteRepository ) GetResource ( ctx context . Context , resourceId string ) ( * models . ResourceFhir , error ) {
resourceUUID , err := uuid . Parse ( resourceId )
if err != nil {
return nil , err
}
queryParam := models . ResourceFhir {
OriginBase : models . OriginBase {
ModelBase : models . ModelBase {
ID : resourceUUID ,
} ,
UserID : sr . GetCurrentUser ( ctx ) . ID ,
} ,
}
var wrappedResourceModel models . ResourceFhir
results := sr . gormClient . WithContext ( ctx ) .
Where ( queryParam ) .
First ( & wrappedResourceModel )
return & wrappedResourceModel , results . Error
}
2022-09-18 01:36:53 -06:00
// 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
}
2022-08-25 19:26:29 -06:00
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2022-09-13 19:02:26 -06:00
// Source
2022-08-25 19:26:29 -06:00
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2022-09-13 19:02:26 -06:00
func ( sr * sqliteRepository ) CreateSource ( ctx context . Context , sourceCreds * models . Source ) error {
sourceCreds . UserID = sr . GetCurrentUser ( ctx ) . ID
2022-08-27 20:34:48 -06:00
2022-09-24 23:18:07 -06:00
if sr . gormClient . WithContext ( ctx ) .
2022-08-30 20:55:27 -06:00
Where ( models . Source {
2022-09-13 19:02:26 -06:00
UserID : sourceCreds . UserID ,
SourceType : sourceCreds . SourceType ,
2022-09-24 23:18:07 -06:00
PatientId : sourceCreds . PatientId } ) . Updates ( sourceCreds ) . RowsAffected == 0 {
return sr . gormClient . WithContext ( ctx ) . Create ( sourceCreds ) . Error
2022-08-27 20:34:48 -06:00
}
return nil
}
2022-09-14 20:59:16 -06:00
func ( sr * sqliteRepository ) GetSource ( ctx context . Context , sourceId string ) ( * models . Source , error ) {
sourceUUID , err := uuid . Parse ( sourceId )
if err != nil {
return nil , err
}
var sourceCred models . Source
results := sr . gormClient . WithContext ( ctx ) .
Where ( models . Source { UserID : sr . GetCurrentUser ( ctx ) . ID , ModelBase : models . ModelBase { ID : sourceUUID } } ) .
First ( & sourceCred )
return & sourceCred , results . Error
}
2022-09-17 01:12:12 -06:00
func ( sr * sqliteRepository ) GetSourceSummary ( ctx context . Context , sourceId string ) ( * models . SourceSummary , error ) {
sourceUUID , err := uuid . Parse ( sourceId )
if err != nil {
return nil , err
}
sourceSummary := & models . SourceSummary { }
source , err := sr . GetSource ( ctx , sourceId )
if err != nil {
return nil , err
}
sourceSummary . Source = source
//group by resource type and return counts
// SELECT source_resource_type as resource_type, COUNT(*) as count FROM resource_fhirs WHERE source_id = "53c1e930-63af-46c9-b760-8e83cbc1abd9" GROUP BY source_resource_type;
2022-09-17 11:14:59 -06:00
var resourceTypeCounts [ ] map [ string ] interface { }
2022-09-17 01:12:12 -06:00
2022-09-17 11:14:59 -06:00
result := sr . gormClient . WithContext ( ctx ) .
2022-09-17 01:12:12 -06:00
Model ( models . ResourceFhir { } ) .
Select ( "source_id, source_resource_type as resource_type, count(*) as count" ) .
Group ( "source_resource_type" ) .
Where ( models . OriginBase {
UserID : sr . GetCurrentUser ( ctx ) . ID ,
SourceID : sourceUUID ,
} ) .
2022-09-17 11:14:59 -06:00
Scan ( & resourceTypeCounts )
if result . Error != nil {
return nil , result . Error
}
2022-09-17 01:12:12 -06:00
2022-09-17 11:14:59 -06:00
sourceSummary . ResourceTypeCounts = resourceTypeCounts
2022-09-17 01:12:12 -06:00
return sourceSummary , nil
}
2022-08-30 20:55:27 -06:00
func ( sr * sqliteRepository ) GetSources ( ctx context . Context ) ( [ ] models . Source , error ) {
2022-08-27 20:34:48 -06:00
2022-09-13 19:02:26 -06:00
var sourceCreds [ ] models . Source
2022-08-27 20:34:48 -06:00
results := sr . gormClient . WithContext ( ctx ) .
2022-09-12 19:20:56 -06:00
Where ( models . Source { UserID : sr . GetCurrentUser ( ctx ) . ID } ) .
2022-09-13 19:02:26 -06:00
Find ( & sourceCreds )
2022-08-27 20:34:48 -06:00
2022-09-13 19:02:26 -06:00
return sourceCreds , results . Error
2022-08-25 19:26:29 -06:00
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func sqlitePragmaString ( pragmas map [ string ] string ) string {
q := url . Values { }
for key , val := range pragmas {
q . Add ( "_pragma" , key + "=" + val )
}
queryStr := q . Encode ( )
if len ( queryStr ) > 0 {
return "?" + queryStr
}
return ""
}