upload-service/internal/handler/batch.go

160 lines
3.4 KiB
Go

package handler
import (
"archive/zip"
"fmt"
"html/template"
"io"
"log"
"net/http"
"strings"
"time"
)
var batchTmpl *template.Template
func (h *Handler) Batch(w http.ResponseWriter, r *http.Request) {
batchID := r.PathValue("id")
if batchID == "" {
http.NotFound(w, r)
return
}
files, err := h.store.GetBatch(batchID)
if err != nil {
log.Printf("db get batch error: %v", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
if len(files) == 0 {
http.NotFound(w, r)
return
}
type fileData struct {
ID string
Filename string
Size int64
ContentType string
UploadedAt string
ThumbType string
PreviewURL string
ViewURL string
DownloadURL string
}
var items []fileData
var totalSize int64
for _, f := range files {
// Skip expired files
if f.ExpiresAt != nil && f.ExpiresAt.Before(time.Now().UTC()) {
continue
}
thumbType := ""
previewURL := ""
if strings.HasPrefix(f.ContentType, "image/") {
thumbType = "image"
} else if strings.HasPrefix(f.ContentType, "video/") {
thumbType = "video"
} else if strings.HasPrefix(f.ContentType, "audio/") {
thumbType = "audio"
} else if f.ContentType == "application/pdf" {
thumbType = "pdf"
}
if thumbType != "" {
if url, err := h.r2.PresignGet(r.Context(), f.R2Key, f.Filename, true); err == nil {
previewURL = url
}
}
totalSize += f.SizeBytes
items = append(items, fileData{
ID: f.ID,
Filename: f.Filename,
Size: f.SizeBytes,
ContentType: f.ContentType,
UploadedAt: f.UploadedAt.Format("Jan 2, 2006 at 3:04 PM"),
ThumbType: thumbType,
PreviewURL: previewURL,
ViewURL: "/f/" + f.ID + "/view",
DownloadURL: "/f/" + f.ID + "/dl",
})
}
if len(items) == 0 {
http.NotFound(w, r)
return
}
data := map[string]any{
"BatchID": batchID,
"Files": items,
"Count": len(items),
"TotalSize": totalSize,
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Cache-Control", "no-cache, must-revalidate")
batchTmpl.Execute(w, data)
}
// BatchDownload streams all batch files as a zip archive.
func (h *Handler) BatchDownload(w http.ResponseWriter, r *http.Request) {
batchID := r.PathValue("id")
if batchID == "" {
http.NotFound(w, r)
return
}
files, err := h.store.GetBatch(batchID)
if err != nil {
log.Printf("db get batch error: %v", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
if len(files) == 0 {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/zip")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.zip"`, batchID))
zw := zip.NewWriter(w)
defer zw.Close()
for _, f := range files {
if f.ExpiresAt != nil && f.ExpiresAt.Before(time.Now().UTC()) {
continue
}
if f.PasswordHash != nil {
continue // skip password-protected files
}
// Get presigned URL for this file
dlURL, err := h.r2.PresignGet(r.Context(), f.R2Key, f.Filename, false)
if err != nil {
log.Printf("batch zip presign error for %s: %v", f.ID, err)
continue
}
// Fetch the file from R2
resp, err := http.Get(dlURL)
if err != nil {
log.Printf("batch zip fetch error for %s: %v", f.ID, err)
continue
}
fw, err := zw.Create(f.Filename)
if err != nil {
resp.Body.Close()
log.Printf("batch zip create entry error: %v", err)
continue
}
io.Copy(fw, resp.Body)
resp.Body.Close()
}
}