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"
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
}
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-09-12 19:20:56 -06:00
func ( sr * sqliteRepository ) GetCurrentUser ( ctx context . Context ) models . User {
ginCtx := ctx . ( * gin . Context )
2022-08-30 20:55:27 -06:00
var currentUser models . User
2022-09-12 19:20:56 -06:00
sr . gormClient . Model ( models . User { } ) . First ( & currentUser , models . User { Username : ginCtx . MustGet ( "AUTH_USERNAME" ) . ( string ) } )
2022-08-30 20:55:27 -06:00
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 {
2022-09-12 19:20:56 -06:00
UserID : sr . GetCurrentUser ( ctx ) . ID ,
2022-09-08 23:53:54 -06:00
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-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-13 19:02:26 -06:00
if sr . gormClient . WithContext ( ctx ) . Model ( & sourceCreds ) .
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 ,
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-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 ""
}