Files
FHIR-Sandbox/app/handlers/dashboard.go
2026-02-20 17:17:26 -05:00

125 lines
4.1 KiB
Go

package handlers
import (
"log"
"net/http"
"github.com/AmanTahiliani/FHIR-Sandbox/app/fhir"
"github.com/AmanTahiliani/FHIR-Sandbox/app/middleware"
"github.com/AmanTahiliani/FHIR-Sandbox/app/models"
)
// HandleDashboard renders the stable patient dashboard.
// On first load (no prior sync), automatically triggers a sync to populate data.
// All other clinical data is read from the local database; no live FHIR calls are
// made here. Use POST /dashboard/sync to refresh data from the EHR.
//
// GET /dashboard
func (h *Handler) HandleDashboard(w http.ResponseWriter, r *http.Request) {
sess := middleware.SessionFromContext(r.Context())
practitionerUser := middleware.UserFromContext(r.Context())
if sess == nil || practitionerUser == nil {
h.handleUnauthorized(w, r)
return
}
ehrURL := sess.EHRURL
patientID := sess.PatientFHIRID
// Allow overriding the patient context via a query parameter.
if overrideID := r.URL.Query().Get("patient_id"); overrideID != "" {
patientID = overrideID
}
// Check if this is the first load and trigger auto-sync
latestSync, err := h.store.LatestSync(patientID, ehrURL)
if err != nil {
log.Printf("handlers: dashboard LatestSync Patient/%s: %v", patientID, err)
}
if latestSync == nil {
// First load — redirect to sync endpoint for auto-sync
syncURL := "/dashboard/sync"
if overrideID := r.URL.Query().Get("patient_id"); overrideID != "" {
syncURL += "?patient_id=" + overrideID
}
http.Redirect(w, r, syncURL, http.StatusSeeOther)
return
}
// Fetch patient demographics from the FHIR server. This is a cheap single
// resource call and keeps the patient card always current.
fhirClient := fhir.NewClient(ehrURL, sess.AccessToken)
patient, err := fhirClient.GetPatient(patientID)
if err != nil {
log.Printf("handlers: dashboard fetch Patient/%s failed: %v", patientID, err)
h.renderError(w, http.StatusBadGateway, "Failed to fetch patient details from the FHIR server.")
return
}
patientUser := fhir.ExtractUserFromPatient(patient, ehrURL)
// Read clinical data from the local database.
observations, err := h.store.ListObservations(patientID, ehrURL)
if err != nil {
log.Printf("handlers: dashboard ListObservations Patient/%s: %v", patientID, err)
// Non-fatal; render with empty slice.
}
conditions, err := h.store.ListConditions(patientID, ehrURL)
if err != nil {
log.Printf("handlers: dashboard ListConditions Patient/%s: %v", patientID, err)
}
docRefs, err := h.store.ListDocumentReferences(patientID, ehrURL)
if err != nil {
log.Printf("handlers: dashboard ListDocumentReferences Patient/%s: %v", patientID, err)
}
medications, err := h.store.ListMedicationRequests(patientID, ehrURL)
if err != nil {
log.Printf("handlers: dashboard ListMedicationRequests Patient/%s: %v", patientID, err)
}
allergies, err := h.store.ListAllergyIntolerances(patientID, ehrURL)
if err != nil {
log.Printf("handlers: dashboard ListAllergyIntolerances Patient/%s: %v", patientID, err)
}
synced := r.URL.Query().Get("synced") == "true"
h.render(w, "dashboard.html", dashboardData{
Patient: patientUser,
Practitioner: practitionerUser,
RawPatient: patient,
Observations: observations,
Conditions: conditions,
DocumentReferences: docRefs,
Medications: medications,
Allergies: allergies,
LatestSync: latestSync,
Session: sess,
Synced: synced,
})
}
// dashboardData is the view model passed to the dashboard template.
type dashboardData struct {
Patient *models.User
Practitioner *models.User
RawPatient *fhir.Patient
Observations []models.Observation
Conditions []models.Condition
DocumentReferences []models.DocumentReference
Medications []models.MedicationRequest
Allergies []models.AllergyIntolerance
LatestSync *models.PatientSync
Session *models.Session
Synced bool
}
// handleUnauthorized redirects to root for dashboard requests.
func (h *Handler) handleUnauthorized(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther)
}