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 { } ,
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
}
record := sr . gormClient . Create ( & user )
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
result := sr . gormClient . Model ( models . User { } ) . Where ( models . User { Username : username } ) . First ( & foundUser )
return & foundUser , result . Error
}
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-08 23:53:54 -06:00
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Resource
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2022-09-08 22:14:03 -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-08 22:14:03 -06:00
if sr . gormClient . Debug ( ) . WithContext ( ctx ) . Model ( & resourceModel ) .
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
} ) . Updates ( & resourceModel ) . RowsAffected == 0 {
sr . logger . Infof ( "resource does not exist, creating: %s %s %s" , resourceModel . GetSourceID ( ) , resourceModel . GetSourceResourceID ( ) , resourceModel . GetSourceResourceType ( ) )
return sr . gormClient . Debug ( ) . Model ( & resourceModel ) . Create ( & resourceModel ) . Error
2022-08-30 20:55:27 -06:00
}
return nil
2022-08-27 09:39:55 -06:00
}
2022-09-08 23:53:54 -06:00
func ( sr * sqliteRepository ) ListResources ( ctx context . Context , sourceResourceType string , sourceResourceId string ) ( [ ] models . ResourceFhir , error ) {
queryParam := models . ResourceFhir {
OriginBase : models . OriginBase {
UserID : sr . GetCurrentUser ( ) . ID ,
SourceResourceType : sourceResourceType ,
} ,
}
if len ( sourceResourceId ) > 0 {
queryParam . SourceResourceID = sourceResourceId
}
var wrappedResourceModels [ ] models . ResourceFhir
results := sr . gormClient . WithContext ( ctx ) .
Where ( queryParam ) .
Find ( & wrappedResourceModels )
return wrappedResourceModels , results . Error
}
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
}
2022-09-10 21:12:29 -06:00
//func (sr *sqliteRepository) GetSource(ctx context.Context, providerId string) (models.Source, error) {
//
// var providerCredentials models.Source
// results := sr.gormClient.WithContext(ctx).
// Where(models.Source{UserID: sr.GetCurrentUser().ID, ProviderId: providerId}).
// Find(&providerCredentials)
//
// return providerCredential, 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 ""
}