diff --git a/backend/pkg/models/background_job.go b/backend/pkg/models/background_job.go index 185a704b..0bc7bcd2 100644 --- a/backend/pkg/models/background_job.go +++ b/backend/pkg/models/background_job.go @@ -13,7 +13,8 @@ type BackgroundJob struct { User User `json:"user,omitempty"` //SECURITY: user and user.id will be set by the repository service UserID uuid.UUID `json:"user_id"` - JobType pkg.BackgroundJobType `json:"job_type"` + JobType pkg.BackgroundJobType `json:"job_type"` + //this should be JSON encoded data from BackgroundJobSyncData or Data datatypes.JSON `gorm:"column:data;type:text;serializer:json" json:"data,omitempty"` JobStatus pkg.BackgroundJobStatus `json:"job_status"` LockedTime *time.Time `json:"locked_time"` diff --git a/backend/pkg/web/handler/background_jobs.go b/backend/pkg/web/handler/background_jobs.go index 3c3a0f84..be0e47e7 100644 --- a/backend/pkg/web/handler/background_jobs.go +++ b/backend/pkg/web/handler/background_jobs.go @@ -239,6 +239,46 @@ func ListBackgroundJobs(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true, "data": backgroundJobs}) } +// CreateBackgroundJobError this function is used to store error data related to a Source/Provider connection operation that fails in the client-side +// - client errors occur when the OAuth provider sends back an error message (error, error_description query string parameters) or when the code -> access token swap results in an error. +// - server side errors occur for a number of reasons (unable to initialize client, unable to store crednetial in db, unable to sync 1 or more FHIR resources from a patient's medical record) +func CreateBackgroundJobError(c *gin.Context) { + + logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry) + databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository) + + var payload models.BackgroundJobSyncData + if err := c.ShouldBindJSON(&payload); err != nil { + logger.Errorln("An error occurred while parsing error data", err) + c.JSON(http.StatusBadRequest, gin.H{"success": false}) + return + } + + //override the job type to be an error + errJsonData, err := json.MarshalIndent(payload, "", " ") + if err != nil { + logger.Errorln("An error occurred re-encoding error data", err) + c.JSON(http.StatusInternalServerError, gin.H{"success": false}) + return + } + now := time.Now() + backgroundJob := models.BackgroundJob{ + JobType: pkg.BackgroundJobTypeSync, + JobStatus: pkg.BackgroundJobStatusFailed, + DoneTime: &now, + LockedTime: &now, + Data: errJsonData, + } + + err = databaseRepo.CreateBackgroundJob(c, &backgroundJob) + if err != nil { + logger.Errorln("An error occurred while creating background job to store client-side error data", err) + c.JSON(http.StatusInternalServerError, gin.H{"success": false}) + return + } + c.JSON(http.StatusOK, gin.H{"success": true}) +} + // Utilities func GetBackgroundContext(ginContext *gin.Context) context.Context { diff --git a/backend/pkg/web/handler/source.go b/backend/pkg/web/handler/source.go index eb738b3e..1a7212ac 100644 --- a/backend/pkg/web/handler/source.go +++ b/backend/pkg/web/handler/source.go @@ -89,9 +89,12 @@ func CreateReconnectSource(c *gin.Context) { summary, err := BackgroundJobSyncResources(GetBackgroundContext(c), logger, databaseRepo, &sourceCred) if err != nil { - err := fmt.Errorf("an error occurred while starting initial sync: %w", err) logger.Errorln(err) - c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()}) + //errors from the background job will be wrapped and stored in the database, lets just return a generic error + // this is also important because these errors: + // 1. are not user facing - longer/scarier for users, and may show information that they are not equipped to resolve themselves. + // 2. lots of duplicate text ("an error occurred while...") due to wrapping as the error bubbles up the codebase. + c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "initial record sync failed. See background jobs page for more details"}) return } @@ -118,7 +121,11 @@ func SourceSync(c *gin.Context) { if err != nil { err := fmt.Errorf("an error occurred while syncing resources: %w", err) logger.Errorln(err) - c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()}) + //errors from the background job will be wrapped and stored in the database, lets just return a generic error + // this is also important because these errors: + // 1. are not user facing - longer/scarier for users, and may show information that they are not equipped to resolve themselves. + // 2. lots of duplicate text ("an error occurred while...") due to wrapping as the error bubbles up the codebase. + c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "record sync failed. See background jobs page for more details"}) return } diff --git a/backend/pkg/web/server.go b/backend/pkg/web/server.go index fcc55f31..cdb21a16 100644 --- a/backend/pkg/web/server.go +++ b/backend/pkg/web/server.go @@ -85,6 +85,7 @@ func (ae *AppEngine) Setup() (*gin.RouterGroup, *gin.Engine) { //secure.GET("/dashboard/:dashboardId", handler.GetDashboard) secure.GET("/jobs", handler.ListBackgroundJobs) + secure.POST("/jobs/error", handler.CreateBackgroundJobError) secure.POST("/query", handler.QueryResourceFhir)