feat: base go mkiso
This commit is contained in:
69
internal/handler/health.go
Normal file
69
internal/handler/health.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
|
||||
"mkiso-server/internal/config"
|
||||
"mkiso-server/pkg/dicom"
|
||||
)
|
||||
|
||||
// Health returns a handler that checks and reports the status of
|
||||
// all external dependencies.
|
||||
func Health(cfg *config.Config) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
deps := []struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Status string `json:"status"`
|
||||
}{
|
||||
{"storescp", cfg.DCMTK.Storescp, ""},
|
||||
{"movescu", cfg.DCMTK.Movescu, ""},
|
||||
{"storescu", cfg.DCMTK.Storescu, ""},
|
||||
}
|
||||
|
||||
allOK := true
|
||||
for i, dep := range deps {
|
||||
if dep.Path == "" {
|
||||
deps[i].Status = "not configured"
|
||||
allOK = false
|
||||
continue
|
||||
}
|
||||
if dicom.FileExists(dep.Path) {
|
||||
// Quick check: can we execute it?
|
||||
cmd := exec.Command(dep.Path, "--version")
|
||||
if err := cmd.Run(); err == nil {
|
||||
deps[i].Status = "ok"
|
||||
} else {
|
||||
deps[i].Status = "executable not found"
|
||||
allOK = false
|
||||
}
|
||||
} else {
|
||||
deps[i].Status = "file not found"
|
||||
allOK = false
|
||||
}
|
||||
}
|
||||
|
||||
// Check microdicom path
|
||||
microdicomOK := dicom.DirExists(cfg.ISO.MicrodicomPath)
|
||||
|
||||
response := map[string]interface{}{
|
||||
"status": "ok",
|
||||
"dependencies": deps,
|
||||
"microdicom_path": cfg.ISO.MicrodicomPath,
|
||||
"microdicom_exists": microdicomOK,
|
||||
"auth_enabled": cfg.Auth.Enabled,
|
||||
}
|
||||
|
||||
statusCode := http.StatusOK
|
||||
if !allOK {
|
||||
response["status"] = "degraded"
|
||||
statusCode = http.StatusOK // still return 200, status field indicates degraded
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
}
|
||||
133
internal/handler/iso.go
Normal file
133
internal/handler/iso.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"mkiso-server/internal/service"
|
||||
)
|
||||
|
||||
// writeJSON is a helper to write a JSON response.
|
||||
func writeJSON(w http.ResponseWriter, status int, v interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
// DownloadSingle handles GET /api/iso/download?accession_number=X
|
||||
// Returns an ISO file as application/octet-stream.
|
||||
func DownloadSingle(isoSvc *service.ISOService) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
acc := r.URL.Query().Get("accession_number")
|
||||
if acc == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "missing accession_number"})
|
||||
return
|
||||
}
|
||||
acc = strings.TrimSpace(acc)
|
||||
|
||||
slog.Info("handling download single", "accession", acc)
|
||||
|
||||
item, err := isoSvc.GenerateISO(r.Context(), acc)
|
||||
if err != nil {
|
||||
slog.Error("ISO generation failed", "accession", acc, "error", err)
|
||||
status := http.StatusInternalServerError
|
||||
msg := "ISO creation failed"
|
||||
if strings.Contains(err.Error(), "no DICOM data") {
|
||||
status = http.StatusNotFound
|
||||
msg = "no DICOM data for accession_number"
|
||||
}
|
||||
writeJSON(w, status, map[string]string{
|
||||
"error": msg,
|
||||
"detail": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
defer item.Cleanup()
|
||||
|
||||
// Stream ISO file
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, item.Filename))
|
||||
|
||||
data, err := os.ReadFile(item.Path)
|
||||
if err != nil {
|
||||
slog.Error("read ISO file failed", "path", item.Path, "error", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to read ISO"})
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(data)
|
||||
slog.Info("ISO download completed", "accession", acc, "size", len(data))
|
||||
}
|
||||
}
|
||||
|
||||
// DownloadMultiple handles GET /api/iso/download-multiple?accession_numbers=X,Y,Z
|
||||
// Returns an ISO file as application/octet-stream.
|
||||
func DownloadMultiple(isoSvc *service.ISOService) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
raw := r.URL.Query().Get("accession_numbers")
|
||||
if raw == "" {
|
||||
// Also check for "accession_number" with comma (backward compat)
|
||||
raw = r.URL.Query().Get("accession_number")
|
||||
}
|
||||
if raw == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "missing accession_numbers"})
|
||||
return
|
||||
}
|
||||
|
||||
accs := parseAccessions(raw)
|
||||
if len(accs) == 0 {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "empty accession_number list"})
|
||||
return
|
||||
}
|
||||
|
||||
slog.Info("handling download multiple", "accessions", accs)
|
||||
|
||||
item, err := isoSvc.GenerateISOMultiple(r.Context(), accs)
|
||||
if err != nil {
|
||||
slog.Error("ISO generation failed", "accessions", accs, "error", err)
|
||||
status := http.StatusInternalServerError
|
||||
msg := "ISO creation failed"
|
||||
if strings.Contains(err.Error(), "no DICOM data") {
|
||||
status = http.StatusNotFound
|
||||
msg = "no DICOM data for accession_numbers"
|
||||
}
|
||||
writeJSON(w, status, map[string]string{
|
||||
"error": msg,
|
||||
"detail": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
defer item.Cleanup()
|
||||
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, item.Filename))
|
||||
|
||||
data, err := os.ReadFile(item.Path)
|
||||
if err != nil {
|
||||
slog.Error("read ISO file failed", "path", item.Path, "error", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to read ISO"})
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(data)
|
||||
slog.Info("ISO download completed (multiple)", "accessions", accs, "size", len(data))
|
||||
}
|
||||
}
|
||||
|
||||
// parseAccessions parses a comma-separated list of accession numbers,
|
||||
// trimming whitespace and filtering empty entries.
|
||||
func parseAccessions(raw string) []string {
|
||||
parts := strings.Split(raw, ",")
|
||||
var result []string
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(p)
|
||||
if p != "" {
|
||||
result = append(result, p)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
63
internal/handler/print.go
Normal file
63
internal/handler/print.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"mkiso-server/internal/service"
|
||||
)
|
||||
|
||||
// PrintISO handles GET /api/iso/print?accession_number=X
|
||||
// This single endpoint handles both single and multi-accession relay.
|
||||
// If accession_number contains commas, it auto-detects multi mode.
|
||||
// Returns JSON response with relay results.
|
||||
func PrintISO(relaySvc *service.RelayService) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
acc := r.URL.Query().Get("accession_number")
|
||||
if acc == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "missing accession_number"})
|
||||
return
|
||||
}
|
||||
|
||||
accs := parseAccessions(acc)
|
||||
if len(accs) == 0 {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "empty accession_number"})
|
||||
return
|
||||
}
|
||||
|
||||
slog.Info("handling print/relay",
|
||||
"accessions", accs,
|
||||
"mode", map[bool]string{true: "multi", false: "single"}[len(accs) > 1],
|
||||
)
|
||||
|
||||
result, err := relaySvc.RelayToCDPublisher(r.Context(), accs)
|
||||
if err != nil {
|
||||
slog.Error("print ISO relay failed", "accessions", accs, "error", err)
|
||||
status := http.StatusBadGateway
|
||||
msg := "DICOM relay failed"
|
||||
|
||||
if strings.Contains(err.Error(), "no DICOM data") {
|
||||
status = http.StatusNotFound
|
||||
msg = "no DICOM data for accession_number"
|
||||
} else if strings.Contains(err.Error(), "storescu relay failed") {
|
||||
msg = "CD Publisher unreachable"
|
||||
}
|
||||
|
||||
writeJSON(w, status, map[string]string{
|
||||
"error": msg,
|
||||
"detail": err.Error(),
|
||||
"destination": relaySvc.Destination(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"status": "ok",
|
||||
"accessions_sent": result.AccessionsSent,
|
||||
"patient_name": result.PatientName,
|
||||
"destination": result.Destination,
|
||||
"files_sent": result.FilesSent,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user