2022-08-25 19:26:29 -06:00
package database
import (
"context"
"fmt"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/glebarez/sqlite"
"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 { } ,
& models . Profile { } ,
2022-08-30 22:36:40 -06:00
& models . Organization { } ,
2022-09-01 19:54:01 -06:00
& models . Encounter { } ,
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-08-27 09:39:55 -06:00
func ( sr * sqliteRepository ) GetCurrentUser ( ) models . User {
2022-08-30 20:55:27 -06:00
var currentUser models . User
sr . gormClient . Model ( models . User { } ) . First ( & currentUser )
return currentUser
}
2022-09-01 19:54:01 -06:00
func ( sr * sqliteRepository ) UpsertResource ( ctx context . Context , resourceModel interface { } ) error {
sr . logger . Infof ( "insert/update (%T) %v" , resourceModel , resourceModel )
switch ( resourceModel ) . ( type ) {
case models . Encounter :
var apiEncounter models . Encounter
apiEncounter = resourceModel . ( models . Encounter )
if sr . gormClient . Debug ( ) . WithContext ( ctx ) . Model ( & apiEncounter ) .
Where ( models . OriginBase {
SourceID : apiEncounter . GetSourceID ( ) ,
SourceResourceID : apiEncounter . GetSourceResourceID ( ) ,
SourceResourceType : apiEncounter . GetSourceResourceType ( ) , //TODO: and UpdatedAt > old UpdatedAt
} ) . Updates ( & apiEncounter ) . RowsAffected == 0 {
sr . logger . Infof ( "organization does not exist, creating: %s %s %s" , apiEncounter . GetSourceID ( ) , apiEncounter . GetSourceResourceID ( ) , apiEncounter . GetSourceResourceType ( ) )
return sr . gormClient . Debug ( ) . Model ( & apiEncounter ) . Create ( & apiEncounter ) . Error
}
case models . Organization :
var apiOrganization models . Organization
apiOrganization = ( resourceModel ) . ( models . Organization )
if sr . gormClient . Debug ( ) . WithContext ( ctx ) . Model ( & apiOrganization ) .
Where ( models . OriginBase {
SourceID : apiOrganization . GetSourceID ( ) ,
SourceResourceID : apiOrganization . GetSourceResourceID ( ) ,
SourceResourceType : apiOrganization . GetSourceResourceType ( ) , //TODO: and UpdatedAt > old UpdatedAt
} ) . Updates ( & apiOrganization ) . RowsAffected == 0 {
sr . logger . Infof ( "organization does not exist, creating: %s %s %s" , apiOrganization . GetSourceID ( ) , apiOrganization . GetSourceResourceID ( ) , apiOrganization . GetSourceResourceType ( ) )
return sr . gormClient . Debug ( ) . Model ( & apiOrganization ) . Create ( & apiOrganization ) . Error
}
case models . Profile :
var apiProfile models . Profile
apiProfile = ( resourceModel ) . ( models . Profile )
if sr . gormClient . Debug ( ) . WithContext ( ctx ) . Model ( & apiProfile ) .
Where ( models . OriginBase {
SourceID : apiProfile . GetSourceID ( ) ,
SourceResourceID : apiProfile . GetSourceResourceID ( ) ,
SourceResourceType : apiProfile . GetSourceResourceType ( ) , //TODO: and UpdatedAt > old UpdatedAt
} ) . Updates ( & apiProfile ) . RowsAffected == 0 {
sr . logger . Infof ( "profile does not exist, creating: %s %s %s" , apiProfile . GetSourceID ( ) , apiProfile . GetSourceResourceID ( ) , apiProfile . GetSourceResourceType ( ) )
return sr . gormClient . Debug ( ) . Model ( & apiProfile ) . Create ( & apiProfile ) . Error
}
default :
return fmt . Errorf ( "unknown model (%T) %v" , resourceModel , resourceModel )
2022-08-30 22:36:40 -06:00
}
return nil
}
func ( sr * sqliteRepository ) UpsertOrganziation ( ctx context . Context , org * models . Organization ) error {
if sr . gormClient . Debug ( ) . WithContext ( ctx ) . Model ( org ) .
Where ( models . OriginBase {
SourceID : org . GetSourceID ( ) ,
SourceResourceID : org . GetSourceResourceID ( ) ,
SourceResourceType : org . GetSourceResourceType ( ) , //TODO: and UpdatedAt > old UpdatedAt
} ) . Updates ( org ) . RowsAffected == 0 {
sr . logger . Infof ( "org does not exist, creating: %s %s %s" , org . GetSourceID ( ) , org . GetSourceResourceID ( ) , org . GetSourceResourceType ( ) )
return sr . gormClient . Debug ( ) . Create ( org ) . Error
2022-08-30 20:55:27 -06:00
}
return nil
2022-08-27 09:39:55 -06:00
}
2022-08-25 19:26:29 -06:00
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2022-08-28 11:51:58 -06:00
// ProviderCredentials
2022-08-25 19:26:29 -06:00
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2022-08-30 20:55:27 -06:00
func ( sr * sqliteRepository ) CreateSource ( ctx context . Context , providerCreds * models . Source ) error {
2022-08-28 11:51:58 -06:00
providerCreds . UserID = sr . GetCurrentUser ( ) . ID
2022-08-27 20:34:48 -06:00
if sr . gormClient . WithContext ( ctx ) . Model ( & providerCreds ) .
2022-08-30 20:55:27 -06:00
Where ( models . Source {
2022-08-28 11:51:58 -06:00
UserID : providerCreds . UserID ,
2022-08-27 20:34:48 -06:00
ProviderId : providerCreds . ProviderId ,
PatientId : providerCreds . PatientId } ) . Updates ( & providerCreds ) . RowsAffected == 0 {
return sr . gormClient . WithContext ( ctx ) . Create ( & providerCreds ) . Error
}
return 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-08-30 20:55:27 -06:00
var providerCredentials [ ] models . Source
2022-08-27 20:34:48 -06:00
results := sr . gormClient . WithContext ( ctx ) .
2022-08-30 20:55:27 -06:00
Where ( models . Source { UserID : sr . GetCurrentUser ( ) . ID } ) .
2022-08-27 20:34:48 -06:00
Find ( & providerCredentials )
return providerCredentials , 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 ""
}