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 }