summaryrefslogtreecommitdiff
path: root/internal/api/handlers_share.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-01 21:10:43 +0300
committerPaul Buetow <paul@buetow.org>2026-05-01 21:10:43 +0300
commitaf29deb33ee25800976b7122236bf7895a5ff39e (patch)
treeb138a6dfe2bb9636ff1c58ff8d255223cd145901 /internal/api/handlers_share.go
parent74c2e0982f336ab135d8b63d26fa49bcd58e57a9 (diff)
refactor(api): split monolithic handlers.go into domain-specific files (task 3)
Split internal/api/handlers.go (1045 lines) to improve KISS/SRP: - handlers_auth.go – bootstrap, login, logout, health, session cookies - handlers_media.go – sets, media CRUD, tags, favorites, notes, progress - handlers_share.go – create/list/revoke shares, share page, share stream - handlers_admin.go – trash, rescan, users, permissions - handlers_file.go – stream, download, thumbnail, regenerate thumbnail Shared helpers (writeJSON, readJSON, pathID, serveFileResult, mimeTypeForFilename, etc.) remain in handlers.go. All tests pass: go test ./... -race -cover.
Diffstat (limited to 'internal/api/handlers_share.go')
-rw-r--r--internal/api/handlers_share.go116
1 files changed, 116 insertions, 0 deletions
diff --git a/internal/api/handlers_share.go b/internal/api/handlers_share.go
new file mode 100644
index 0000000..9fc2974
--- /dev/null
+++ b/internal/api/handlers_share.go
@@ -0,0 +1,116 @@
+package api
+
+import (
+ "errors"
+ "net/http"
+ "strings"
+ "time"
+
+ "codeberg.org/snonux/player/internal/service"
+)
+
+// ------------------------------------------------------------------
+// Share routes
+// ------------------------------------------------------------------
+
+func (s *Server) handleCreateShare(w http.ResponseWriter, r *http.Request) {
+ if !requireService(w, s.mediaSvc) {
+ return
+ }
+ id := pathID(r, "id")
+ if id == 0 {
+ writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid media id"})
+ return
+ }
+ expiresAt := time.Now().Add(time.Duration(s.cfg.ShareDefaultExpiryDays) * 24 * time.Hour)
+ share, err := s.mediaSvc.CreateShare(r.Context(), userIDFromContext(r), id, expiresAt)
+ if err != nil {
+ writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+ return
+ }
+ writeJSON(w, http.StatusOK, share)
+}
+
+func (s *Server) handleListShares(w http.ResponseWriter, r *http.Request) {
+ if !requireService(w, s.mediaSvc) {
+ return
+ }
+ id := pathID(r, "id")
+ if id == 0 {
+ writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid media id"})
+ return
+ }
+ shares, err := s.mediaSvc.ListShares(r.Context(), id, userIDFromContext(r))
+ if err != nil {
+ writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+ return
+ }
+ writeJSON(w, http.StatusOK, shares)
+}
+
+func (s *Server) handleRevokeShare(w http.ResponseWriter, r *http.Request) {
+ if !requireService(w, s.mediaSvc) {
+ return
+ }
+ token := r.PathValue("token")
+ if token == "" {
+ writeJSON(w, http.StatusBadRequest, map[string]string{"error": "token required"})
+ return
+ }
+ if err := s.mediaSvc.RevokeShare(r.Context(), token, userIDFromContext(r)); err != nil {
+ writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+ return
+ }
+ writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
+}
+
+func (s *Server) handleSharePage(w http.ResponseWriter, r *http.Request) {
+ if !requireService(w, s.mediaSvc) {
+ return
+ }
+ token := r.PathValue("token")
+ share, err := s.mediaSvc.ValidateShareToken(r.Context(), token)
+ if err != nil || share == nil {
+ if err != nil && errors.Is(err, service.ErrShareExpired) {
+ http.Error(w, "gone", http.StatusGone)
+ return
+ }
+ http.Error(w, "not found", http.StatusNotFound)
+ return
+ }
+
+ w.Header().Set("Cache-Control", "no-store")
+ w.Header().Set("Vary", "Accept")
+
+ accept := r.Header.Get("Accept")
+ if strings.Contains(accept, "text/html") || accept == "" {
+ s.serveFile(w, r, "share.html")
+ return
+ }
+ writeJSON(w, http.StatusOK, share)
+}
+
+func (s *Server) handleShareStream(w http.ResponseWriter, r *http.Request) {
+ if !requireService(w, s.mediaSvc) {
+ return
+ }
+ token := r.PathValue("token")
+ res, err := s.mediaSvc.StreamSharedMedia(r.Context(), token)
+ if err != nil {
+ if errors.Is(err, service.ErrShareExpired) {
+ http.Error(w, "gone", http.StatusGone)
+ return
+ }
+ if errors.Is(err, service.ErrShareNotFound) || errors.Is(err, service.ErrMediaNotFound) {
+ http.Error(w, "not found", http.StatusNotFound)
+ return
+ }
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if res == nil {
+ http.Error(w, "not found", http.StatusNotFound)
+ return
+ }
+ s.serveFileResult(w, r, res, false)
+}