Files
wijtransferen/backend/internal/downloads/handler.go
T
2026-08-22 02:59:16 +02:00

146 lines
4.7 KiB
Go

package downloads
import (
"encoding/json"
"net/http"
"strings"
"time"
"github.com/example/sndit/backend/internal/auth"
"github.com/example/sndit/backend/internal/galleries"
"github.com/example/sndit/backend/internal/media"
"github.com/example/sndit/backend/internal/storage"
"github.com/google/uuid"
)
type Handler struct {
galleries *galleries.Repository
media *media.Repository
storage storage.Storage
auth *auth.Service
service *Service
}
func NewHandler(galleryRepository *galleries.Repository, mediaRepository *media.Repository, objectStorage storage.Storage, authService *auth.Service, service *Service) *Handler {
return &Handler{galleries: galleryRepository, media: mediaRepository, storage: objectStorage, auth: authService, service: service}
}
func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("POST /api/public/galleries/{slug}/media/{mediaId}/download", h.Download)
mux.HandleFunc("POST /api/public/galleries/{slug}/download-all", h.DownloadAll)
mux.HandleFunc("GET /api/public/galleries/{slug}/download-all/{jobId}", h.DownloadAllStatus)
}
func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
record, err := h.publicRecord(r)
if err != nil {
writeError(w, http.StatusNotFound, "gallery not found")
return
}
if !record.DownloadsEnabled {
writeError(w, http.StatusForbidden, "downloads are disabled")
return
}
mediaID, err := uuid.Parse(r.PathValue("mediaId"))
if err != nil {
writeError(w, http.StatusBadRequest, "invalid media id")
return
}
item, err := h.media.GetByID(r.Context(), mediaID)
if err != nil || item.GalleryID != record.ID || item.ProcessingStatus != media.StatusReady {
writeError(w, http.StatusNotFound, "media not found")
return
}
url, err := h.downloadURL(r, item)
if err != nil {
writeError(w, http.StatusInternalServerError, "could not create download")
return
}
visitorID := h.auth.EnsureVisitor(w, r)
_ = h.media.RecordDownload(r.Context(), record.ID, &mediaID, visitorID)
writeJSON(w, http.StatusOK, map[string]string{"url": url})
}
func (h *Handler) DownloadAll(w http.ResponseWriter, r *http.Request) {
record, err := h.publicRecord(r)
if err != nil {
writeError(w, http.StatusNotFound, "gallery not found")
return
}
if !record.DownloadAllEnabled || !record.DownloadsEnabled {
writeError(w, http.StatusForbidden, "gallery downloads are disabled")
return
}
visitorID := h.auth.EnsureVisitor(w, r)
job, err := h.service.Create(r.Context(), record.ID, visitorID)
if err != nil {
writeError(w, http.StatusInternalServerError, "could not start gallery download")
return
}
writeJSON(w, http.StatusAccepted, map[string]string{"jobId": job.ID.String(), "status": job.Status})
}
func (h *Handler) DownloadAllStatus(w http.ResponseWriter, r *http.Request) {
record, err := h.publicRecord(r)
if err != nil {
writeError(w, http.StatusNotFound, "gallery not found")
return
}
if !record.DownloadAllEnabled || !record.DownloadsEnabled {
writeError(w, http.StatusForbidden, "gallery downloads are disabled")
return
}
jobID, err := uuid.Parse(r.PathValue("jobId"))
if err != nil {
writeError(w, http.StatusBadRequest, "invalid download job id")
return
}
visitorID := h.auth.EnsureVisitor(w, r)
job, err := h.service.Get(r.Context(), jobID, record.ID, visitorID)
if err != nil {
writeError(w, http.StatusNotFound, "download job not found")
return
}
response := map[string]any{"jobId": job.ID.String(), "status": job.Status}
if job.Error != "" {
response["error"] = job.Error
}
if job.Status == StatusReady {
url, err := h.storage.CreateDownloadURL(r.Context(), job.StorageKey, time.Hour)
if err != nil {
writeError(w, http.StatusInternalServerError, "could not create download URL")
return
}
response["url"] = url
}
writeJSON(w, http.StatusOK, response)
}
func (h *Handler) publicRecord(r *http.Request) (galleries.GalleryRecord, error) {
record, err := h.galleries.GetPublicBySlug(r.Context(), strings.TrimSpace(r.PathValue("slug")))
if err != nil || record.IsExpired() {
return galleries.GalleryRecord{}, galleries.ErrNotFound
}
if record.PasswordHash != "" && !h.auth.HasGalleryAccess(r, record.Slug) {
return galleries.GalleryRecord{}, galleries.ErrNotFound
}
return record, nil
}
func (h *Handler) downloadURL(r *http.Request, item media.Record) (string, error) {
if item.ExternalURL != "" {
return item.ExternalURL, nil
}
return h.storage.CreateDownloadURL(r.Context(), item.StorageKey, time.Hour)
}
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{"error": message})
}
func writeJSON(w http.ResponseWriter, status int, value any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(value)
}