You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
420 lines
9.5 KiB
420 lines
9.5 KiB
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
|
|
}
|
|
|