package main import ( "context" "io" "log" "net/http" "os" "path" "strconv" "strings" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" "whiteboxsystems.nl/openkvpoc/his/model" "whiteboxsystems.nl/openkvpoc/openkv" "whiteboxsystems.nl/openkvpoc/sharedmodel" ) type HISServer struct { srv *http.Server inited bool data *gorm.DB stopTasks chan struct{} } func (srv *HISServer) LoadData(location string) error { var err error srv.data, err = model.GetDB(location) return err } func (srv *HISServer) Addr() string { if srv.srv == nil { return "" } return srv.srv.Addr } func (srv *HISServer) ListenAndServe() { if !srv.inited { srv.init() } log.Println("Listening on %v", srv.srv.Addr) srv.srv.ListenAndServe() } func (srv *HISServer) Shutdown(ctx context.Context) error { return srv.srv.Shutdown(ctx) } func (srv *HISServer) init() { r := srv.srv.Handler.(*gin.Engine) r.LoadHTMLGlob("templates/*") r.Static("/assets", "./assets") r.Use(srv.Authenticate) r.GET("/", func(c *gin.Context) { c.Redirect(301, "/ui") }) r.GET("/ui", srv.GetIndex) r.GET("/ui/*page", srv.GetIndex) r.GET("/api/patients", srv.GetPatients) r.POST("/api/patients/:id/consent", srv.UpdateConsent) r.POST("/api/patients/:id/consent/:consentID", srv.DeleteConsent) r.GET("/api/connections", srv.GetConnections) r.POST("/api/connections", srv.NewConnection) r.POST("/api/connections/:id/activate", srv.ActivateConnection) r.POST("/api/connections/:id/services", srv.ModService) r.GET("/api/connections/:id", srv.GetConnection) r.GET("/api/services", srv.GetServices) r.POST("/api/services/:id/subscriptions", srv.UpdateSubscription) r.Use(srv.Authenticate) r.GET("/external/api/patients/:id", srv.GetPatient) r.GET("/external/fhir/Patient", srv.GetFHIRPatient) srv.inited = true ticker := time.NewTicker(30 * time.Second) srv.stopTasks = make(chan struct{}) srv.TaskOptOut() go func() { for { select { case <-ticker.C: srv.TaskOptOut() case <-srv.stopTasks: ticker.Stop() return } } }() } func (srv *HISServer) GetIndex(c *gin.Context) { c.HTML(http.StatusOK, "index.html", gin.H{}) } func (srv *HISServer) TaskOptOut() { services := []model.Service{} srv.data.Where("subscription_policy = ?", openkv.SubscriptionPolicy_optout).Preload("Connection").Preload("Connection.AuthConfig").Preload("Subscriptions").Find(&services) patients := []model.Patient{} srv.data.Find(&patients) for _, service := range services { activePatients := []model.Patient{} for _, p := range patients { include := true for _, sub := range service.Subscriptions { if sub.ID == p.ID { include = false } } if include { activePatients = append(activePatients, p) } } _, err := srv.subscribePatients(service.Connection, &service, true, activePatients) if err != nil { log.Println("active patients for optout (%v) err: %v", service.Name, err) } else { log.Printf("sync patients for %v: %v patients subscribed", service.Name, len(activePatients)) } inactivePatients := service.Subscriptions _, err = srv.subscribePatients(service.Connection, &service, false, inactivePatients) if err != nil { log.Println("inactive patients for optout (%v) err: %v", service.Name, err) } else { log.Printf("sync patients for %v: %v patients unsubscribed", service.Name, len(inactivePatients)) } } } func (srv *HISServer) Authenticate(c *gin.Context) { if !strings.HasPrefix(c.Request.RequestURI, "/external/") { return } authHeader := c.Request.Header.Get("Authorization") log.Printf("authHeader: %v", authHeader) authConfig := &sharedmodel.AuthConfig{} if err := srv.data.Where("raw = ?", authHeader).First(authConfig).Error; err != nil { c.Status(401) c.Abort() return } if strings.HasPrefix(c.Request.RequestURI, "/external/api") { if srv.data.Where("auth_config_id = ? and service_id like ?", authConfig.ID, "wbx:%").First(&model.Service{}).Error == nil { return } } if strings.HasPrefix(c.Request.RequestURI, "/external/fhir") { if srv.data.Where("auth_config_id = ? and service_id like ?", authConfig.ID, "%:dvza").First(&model.Service{}).Error == nil { return } } c.Status(401) c.Abort() } func (srv *HISServer) GetPatients(c *gin.Context) { patients := []model.Patient{} if err := srv.data.Preload("Consent").Find(&patients).Error; err != nil { c.AbortWithError(500, err) return } c.JSON(200, patients) } func (srv *HISServer) GetConnections(c *gin.Context) { connections := []model.Connection{} if err := srv.data.Preload("AuthConfig").Preload("Services").Find(&connections).Error; err != nil { c.AbortWithError(500, err) return } c.JSON(200, connections) } func (srv *HISServer) GetServices(c *gin.Context) { services := []model.Service{} if err := srv.data.Preload("Connection").Preload("Subscriptions").Find(&services).Error; err != nil { c.AbortWithError(500, err) return } c.JSON(200, services) } func (srv *HISServer) GetConnection(c *gin.Context) { connID := c.Param("id") connection := &model.Connection{} if err := srv.data.Where("id = ?", connID).Preload("AuthConfig").Preload("Services").Find(connection).Error; err != nil { c.AbortWithError(404, err) return } serviceMeta, err := srv.listMeta(connection) if err != nil { c.AbortWithError(400, err) return } c.JSON(200, map[string]interface{}{"connection": connection, "meta": serviceMeta.Services}) } func (srv *HISServer) ModService(c *gin.Context) { var payload struct { Active bool `json:"active"` Service string `json:"service"` } c.BindJSON(&payload) connID := c.Param("id") connection := &model.Connection{} if err := srv.data.Where("id = ?", connID).Preload("AuthConfig").Preload("Services").Find(connection).Error; err != nil { c.AbortWithError(404, err) return } err := srv.enableService(connection, payload.Service, payload.Active) if err != nil { log.Println("err: %v", err) c.AbortWithError(400, err) return } c.Status(200) } func (srv *HISServer) applyPolicy(sub bool, service *model.Service) bool { if service.SubscriptionPolicy == openkv.SubscriptionPolicy_optout { return !sub } return sub } func (srv *HISServer) UpdateConsent(c *gin.Context) { consent := &model.Consent{} c.BindJSON(consent) pId, _ := strconv.ParseUint(c.Param("id"), 10, 64) consent.PatientID = uint(pId) if consent.ID != 0 { srv.data.Save(consent) c.Status(200) } else { srv.data.Create(consent) c.Status(201) } } func (srv *HISServer) DeleteConsent(c *gin.Context) { consent := &model.Consent{} srv.data.Unscoped().Where("id = ? and patient_id = ?", c.Param("consentID"), c.Param("id")).Delete(consent) c.Status(200) } func (srv *HISServer) UpdateSubscription(c *gin.Context) { var payload struct { Active bool `json:"active"` Patient uint `json:"patient"` } c.BindJSON(&payload) serviceID := c.Param("id") service := &model.Service{} if err := srv.data.Where("id = ?", serviceID).Preload("Connection").Preload("Connection.AuthConfig").Find(service).Error; err != nil { c.AbortWithError(404, err) return } sub := &model.Patient{} srv.data.Model(service).Where("id = ?", payload.Patient).Association("Subscriptions").Find(sub) if payload.Active && sub.ID != 0 { log.Printf("No update needed: %v %v", payload.Patient, service.ServiceID) c.Status(200) return } else if !payload.Active && sub.ID == 0 { log.Printf("No update needed: %v %v", payload.Patient, service.ServiceID) c.Status(200) return } patient := &model.Patient{} if err := srv.data.Where("id = ?", payload.Patient).Find(patient).Error; err != nil { c.AbortWithError(404, err) return } _, err := srv.subscribePatients(service.Connection, service, srv.applyPolicy(payload.Active, service), []model.Patient{*patient}) if err != nil { log.Println("err: %v", err) c.AbortWithError(400, err) return } if payload.Active { srv.data.Model(service).Association("Subscriptions").Append(patient) } else { srv.data.Model(service).Association("Subscriptions").Delete(patient) } c.Status(201) } func (srv *HISServer) NewConnection(c *gin.Context) { var payload struct { URL string `json:"url"` } c.BindJSON(&payload) conn, err := srv.register(payload.URL) if err != nil { c.AbortWithError(400, err) return } c.JSON(201, conn) } func (srv *HISServer) ActivateConnection(c *gin.Context) { connID := c.Param("id") connection := &model.Connection{} if err := srv.data.Preload("AuthConfig").Where("id = ?", connID).First(connection).Error; err != nil { c.AbortWithError(404, err) return } var payload struct { PSK string `json:"psk"` } c.BindJSON(&payload) conn, err := srv.activate(connection, payload.PSK) if err != nil { c.AbortWithError(400, err) return } c.JSON(201, conn) } func (srv *HISServer) GetPatient(c *gin.Context) { id := c.Param("id") patient := &model.Patient{} if err := srv.data.Where("patient_id = ?", id).First(patient).Error; err == nil { f, err := os.Open(path.Join("./data/patients", patient.FileBase+".edi")) if err != nil { c.Error(err) return } io.Copy(c.Writer, f) return } c.JSON(404, nil) } func (srv *HISServer) GetFHIRPatient(c *gin.Context) { id := c.Query("id") patient := &model.Patient{} if err := srv.data.Where("patient_id = ?", id).First(patient).Error; err == nil { f, err := os.Open(path.Join("./data/patients", patient.FileBase+".fhir.json")) if err != nil { c.Error(err) return } io.Copy(c.Writer, f) return } c.JSON(404, nil) } func NewServer(addr string) *HISServer { srv := &HISServer{srv: &http.Server{ Addr: addr, Handler: gin.Default(), }} return srv }