diff --git a/internal/web/handlers.go b/internal/web/handlers.go index 1baf223..91176e0 100644 --- a/internal/web/handlers.go +++ b/internal/web/handlers.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "log" + "mime/multipart" "net/http" "os" "path/filepath" @@ -202,19 +203,52 @@ func (h *Handler) uploadSubmit(w http.ResponseWriter, r *http.Request) { return } - file, header, err := r.FormFile("book") - if err != nil { + var headers []*multipart.FileHeader + if r.MultipartForm != nil { + headers = r.MultipartForm.File["book"] + } + if len(headers) == 0 { render(w, r.Context(), views.UploadPage("Keine Datei ausgewählt.")) return } - defer file.Close() - ext := strings.ToLower(filepath.Ext(header.Filename)) - if ext != ".epub" { - render(w, r.Context(), views.UploadPage("Nur EPUB-Dateien erlaubt.")) + var errs []string + saved := 0 + for _, header := range headers { + if msg := h.saveUpload(header); msg != "" { + errs = append(errs, fmt.Sprintf("%s: %s", header.Filename, msg)) + } else { + saved++ + } + } + + if len(errs) > 0 { + msg := strings.Join(errs, " ") + if saved > 0 { + msg = fmt.Sprintf("%d von %d Dateien hochgeladen. %s", saved, len(headers), msg) + } + render(w, r.Context(), views.UploadPage(msg)) return } + http.Redirect(w, r, "/", http.StatusSeeOther) +} + +// saveUpload validates and persists a single uploaded file. It returns a +// user-facing German error message, or "" on success. +func (h *Handler) saveUpload(header *multipart.FileHeader) string { + ext := strings.ToLower(filepath.Ext(header.Filename)) + if ext != ".epub" { + return "Nur EPUB-Dateien erlaubt." + } + + file, err := header.Open() + if err != nil { + log.Printf("upload: open %q failed: %v", header.Filename, err) + return "Datei konnte nicht gelesen werden." + } + defer file.Close() + // Sanitize filename: keep only base name, no path components. safeFilename := filepath.Base(header.Filename) destPath := filepath.Join(h.lib.BooksDir(), safeFilename) @@ -223,28 +257,24 @@ func (h *Handler) uploadSubmit(w http.ResponseWriter, r *http.Request) { cleanBase := filepath.Clean(h.lib.BooksDir()) cleanDest := filepath.Clean(destPath) if !strings.HasPrefix(cleanDest, cleanBase+string(filepath.Separator)) { - render(w, r.Context(), views.UploadPage("Ungültiger Dateiname.")) - return + return "Ungültiger Dateiname." } out, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644) if err != nil { if os.IsExist(err) { - render(w, r.Context(), views.UploadPage(fmt.Sprintf("Datei '%s' existiert bereits.", safeFilename))) - return + return fmt.Sprintf("Datei '%s' existiert bereits.", safeFilename) } log.Printf("upload: create %q failed: %v", destPath, err) - http.Error(w, "Datei konnte nicht gespeichert werden", http.StatusInternalServerError) - return + return "Datei konnte nicht gespeichert werden." } defer out.Close() if _, err := io.Copy(out, file); err != nil { log.Printf("upload: write %q failed: %v", destPath, err) _ = os.Remove(destPath) - http.Error(w, "Upload fehlgeschlagen", http.StatusInternalServerError) - return + return "Upload fehlgeschlagen." } - http.Redirect(w, r, "/", http.StatusSeeOther) + return "" } diff --git a/views/pages.templ b/views/pages.templ index d358e50..e71a73d 100644 --- a/views/pages.templ +++ b/views/pages.templ @@ -69,16 +69,16 @@ templ LoginPage(errMsg string) { } templ UploadPage(errMsg string) { - @Layout("Buch hochladen", "/") { + @Layout("Bücher hochladen", "/") {
-

Buch hochladen

+

Bücher hochladen

if errMsg != "" {

{ errMsg }

}
- - + +
} diff --git a/views/pages_templ.go b/views/pages_templ.go index 666c97c..e75243b 100644 --- a/views/pages_templ.go +++ b/views/pages_templ.go @@ -239,7 +239,7 @@ func UploadPage(errMsg string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

Buch hochladen

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

Bücher hochladen

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -262,13 +262,13 @@ func UploadPage(errMsg string) templ.Component { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = Layout("Buch hochladen", "/").Render(templ.WithChildren(ctx, templ_7745c5c3_Var10), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = Layout("Bücher hochladen", "/").Render(templ.WithChildren(ctx, templ_7745c5c3_Var10), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }