160 lines
3.4 KiB
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()
|
|
}
|
|
}
|