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", "foreign_keys": "ON", }) 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")) //TODO: automigrate for now err = database.AutoMigrate(&models.User{}) if err != nil { return nil, fmt.Errorf("Failed to automigrate! - %v", err) } err = database.AutoMigrate(&models.ProviderCredential{}) if err != nil { return nil, fmt.Errorf("Failed to automigrate! - %v", err) } // 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) } 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 } func (sr *sqliteRepository) GetCurrentUser() models.User { return models.User{Model: gorm.Model{ID: 1}} } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // ProviderCredentials //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// func (sr *sqliteRepository) CreateProviderCredentials(ctx context.Context, providerCreds *models.ProviderCredential) error { providerCreds.UserID = sr.GetCurrentUser().ID if sr.gormClient.WithContext(ctx).Model(&providerCreds). Where(models.ProviderCredential{ UserID: providerCreds.UserID, ProviderId: providerCreds.ProviderId, PatientId: providerCreds.PatientId}).Updates(&providerCreds).RowsAffected == 0 { return sr.gormClient.WithContext(ctx).Create(&providerCreds).Error } return nil } func (sr *sqliteRepository) GetProviderCredentials(ctx context.Context) ([]models.ProviderCredential, error) { var providerCredentials []models.ProviderCredential results := sr.gormClient.WithContext(ctx). Where(models.ProviderCredential{UserID: sr.GetCurrentUser().ID}). Find(&providerCredentials) return providerCredentials, results.Error } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 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 "" }