summaryrefslogtreecommitdiff
path: root/internal/api
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-02 09:33:22 +0300
committerPaul Buetow <paul@buetow.org>2026-05-02 09:33:22 +0300
commit95c6ce72bdf16ead39d0df1ee27b0474441fbe4f (patch)
treef6823246f8e7f85db2233fec461159005ffadc41 /internal/api
parent4dd62a8b61c56ef28f5f0cab58b8c2da94806717 (diff)
fix:x
Diffstat (limited to 'internal/api')
-rw-r--r--internal/api/handlers_more_test.go36
-rw-r--r--internal/api/handlers_share.go60
-rw-r--r--internal/api/handlers_test.go7
-rw-r--r--internal/api/server.go1
4 files changed, 87 insertions, 17 deletions
diff --git a/internal/api/handlers_more_test.go b/internal/api/handlers_more_test.go
index 8a8303d..23e94c7 100644
--- a/internal/api/handlers_more_test.go
+++ b/internal/api/handlers_more_test.go
@@ -951,7 +951,7 @@ func TestServer_SharePage(t *testing.T) {
t.Run("service error", func(t *testing.T) {
ms := &service.MockMediaService{
- ValidateShareTokenFunc: func(ctx context.Context, token string) (*model.Share, error) {
+ GetSharedMediaFunc: func(ctx context.Context, token string) (*service.GetSharedMediaResult, error) {
return nil, errors.New("boom")
},
}
@@ -966,7 +966,7 @@ func TestServer_SharePage(t *testing.T) {
t.Run("not found", func(t *testing.T) {
ms := &service.MockMediaService{
- ValidateShareTokenFunc: func(ctx context.Context, token string) (*model.Share, error) {
+ GetSharedMediaFunc: func(ctx context.Context, token string) (*service.GetSharedMediaResult, error) {
return nil, nil
},
}
@@ -981,7 +981,7 @@ func TestServer_SharePage(t *testing.T) {
t.Run("expired", func(t *testing.T) {
ms := &service.MockMediaService{
- ValidateShareTokenFunc: func(ctx context.Context, token string) (*model.Share, error) {
+ GetSharedMediaFunc: func(ctx context.Context, token string) (*service.GetSharedMediaResult, error) {
return nil, service.ErrShareExpired
},
}
@@ -996,8 +996,12 @@ func TestServer_SharePage(t *testing.T) {
t.Run("html default accept", func(t *testing.T) {
ms := &service.MockMediaService{
- ValidateShareTokenFunc: func(ctx context.Context, token string) (*model.Share, error) {
- return &model.Share{Token: "abc", MediaID: 1}, nil
+ GetSharedMediaFunc: func(ctx context.Context, token string) (*service.GetSharedMediaResult, error) {
+ return &service.GetSharedMediaResult{
+ Media: &model.Media{ID: 1, FileName: "share.mp4", Type: model.MediaTypeVideo, Duration: 120},
+ StreamURL: "/s/abc/stream",
+ ThumbURL: "/s/abc/thumbnail",
+ }, nil
},
}
srv := newTestServer(t, buildSessionStore(1), nil, nil, cfg, ms, nil, nil, nil, fs)
@@ -1019,8 +1023,12 @@ func TestServer_SharePage(t *testing.T) {
t.Run("html explicit accept", func(t *testing.T) {
ms := &service.MockMediaService{
- ValidateShareTokenFunc: func(ctx context.Context, token string) (*model.Share, error) {
- return &model.Share{Token: "abc", MediaID: 1}, nil
+ GetSharedMediaFunc: func(ctx context.Context, token string) (*service.GetSharedMediaResult, error) {
+ return &service.GetSharedMediaResult{
+ Media: &model.Media{ID: 1, FileName: "share.mp4", Type: model.MediaTypeVideo, Duration: 120},
+ StreamURL: "/s/abc/stream",
+ ThumbURL: "/s/abc/thumbnail",
+ }, nil
},
}
srv := newTestServer(t, buildSessionStore(1), nil, nil, cfg, ms, nil, nil, nil, fs)
@@ -1039,8 +1047,12 @@ func TestServer_SharePage(t *testing.T) {
t.Run("json accept", func(t *testing.T) {
ms := &service.MockMediaService{
- ValidateShareTokenFunc: func(ctx context.Context, token string) (*model.Share, error) {
- return &model.Share{Token: "abc", MediaID: 1}, nil
+ GetSharedMediaFunc: func(ctx context.Context, token string) (*service.GetSharedMediaResult, error) {
+ return &service.GetSharedMediaResult{
+ Media: &model.Media{ID: 1, FileName: "share.mp4", Type: model.MediaTypeVideo, Duration: 120},
+ StreamURL: "/s/abc/stream",
+ ThumbURL: "/s/abc/thumbnail",
+ }, nil
},
}
srv := newTestServer(t, buildSessionStore(1), nil, nil, cfg, ms, nil, nil, nil, fs)
@@ -1055,12 +1067,12 @@ func TestServer_SharePage(t *testing.T) {
if !strings.Contains(ct, "application/json") {
t.Fatalf("expected application/json content type, got %q", ct)
}
- var body model.Share
+ var body service.GetSharedMediaResult
if err := json.Unmarshal(rr.Body.Bytes(), &body); err != nil {
t.Fatalf("expected JSON body: %v", err)
}
- if body.Token != "abc" {
- t.Fatalf("unexpected token %q", body.Token)
+ if body.StreamURL != "/s/abc/stream" {
+ t.Fatalf("unexpected stream_url %q", body.StreamURL)
}
})
}
diff --git a/internal/api/handlers_share.go b/internal/api/handlers_share.go
index 9fc2974..3eac87b 100644
--- a/internal/api/handlers_share.go
+++ b/internal/api/handlers_share.go
@@ -1,8 +1,11 @@
package api
import (
+ "encoding/json"
"errors"
+ "io"
"net/http"
+ "path/filepath"
"strings"
"time"
@@ -69,8 +72,8 @@ func (s *Server) handleSharePage(w http.ResponseWriter, r *http.Request) {
return
}
token := r.PathValue("token")
- share, err := s.mediaSvc.ValidateShareToken(r.Context(), token)
- if err != nil || share == nil {
+ res, err := s.mediaSvc.GetSharedMedia(r.Context(), token)
+ if err != nil || res == nil {
if err != nil && errors.Is(err, service.ErrShareExpired) {
http.Error(w, "gone", http.StatusGone)
return
@@ -83,11 +86,58 @@ func (s *Server) handleSharePage(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Vary", "Accept")
accept := r.Header.Get("Accept")
- if strings.Contains(accept, "text/html") || accept == "" {
- s.serveFile(w, r, "share.html")
+ if strings.Contains(accept, "application/json") {
+ writeJSON(w, http.StatusOK, res)
return
}
- writeJSON(w, http.StatusOK, share)
+
+ // Serve HTML page with media metadata injected.
+ f, err := s.staticFS.Open("share.html")
+ if err != nil {
+ http.Error(w, "not found", http.StatusNotFound)
+ return
+ }
+ defer f.Close()
+ stat, err := f.Stat()
+ if err != nil {
+ http.Error(w, "not found", http.StatusNotFound)
+ return
+ }
+ var buf strings.Builder
+ if _, err := io.Copy(&buf, f); err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+ html := buf.String()
+ data, _ := json.Marshal(res)
+ html = strings.Replace(html, "<!--SHARE_MEDIA-->", string(data), 1)
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ http.ServeContent(w, r, "share.html", stat.ModTime(), strings.NewReader(html))
+}
+
+func (s *Server) handleShareThumbnail(w http.ResponseWriter, r *http.Request) {
+ if !requireService(w, s.mediaSvc) {
+ return
+ }
+ token := r.PathValue("token")
+ res, err := s.mediaSvc.GetSharedMedia(r.Context(), token)
+ if err != nil || res == nil {
+ if err != nil && errors.Is(err, service.ErrShareExpired) {
+ http.Error(w, "gone", http.StatusGone)
+ return
+ }
+ http.Error(w, "not found", http.StatusNotFound)
+ return
+ }
+ if !res.HasThumb || res.Media == nil || res.Media.ThumbnailPath == "" {
+ http.Error(w, "not found", http.StatusNotFound)
+ return
+ }
+ fr := &service.FileResult{
+ Path: res.Media.ThumbnailPath,
+ FileName: filepath.Base(res.Media.ThumbnailPath),
+ }
+ s.serveFileResult(w, r, fr, false)
}
func (s *Server) handleShareStream(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/api/handlers_test.go b/internal/api/handlers_test.go
index 41215c9..b574e61 100644
--- a/internal/api/handlers_test.go
+++ b/internal/api/handlers_test.go
@@ -1081,6 +1081,13 @@ func TestServer_Shares(t *testing.T) {
RevokeShareFunc: func(ctx context.Context, token string, userID int64) error {
return nil
},
+ GetSharedMediaFunc: func(ctx context.Context, token string) (*service.GetSharedMediaResult, error) {
+ return &service.GetSharedMediaResult{
+ Media: &model.Media{ID: 1, FileName: "x.mp4", Type: model.MediaTypeVideo, Duration: 120},
+ StreamURL: "/s/abc/stream",
+ ThumbURL: "/s/abc/thumbnail",
+ }, nil
+ },
ValidateShareTokenFunc: func(ctx context.Context, token string) (*model.Share, error) {
return &model.Share{Token: token, MediaID: 1}, nil
},
diff --git a/internal/api/server.go b/internal/api/server.go
index 0ea96af..6c15c78 100644
--- a/internal/api/server.go
+++ b/internal/api/server.go
@@ -94,6 +94,7 @@ func (s *Server) routes() {
// Public share routes
s.mux.HandleFunc("GET /s/{token}", s.handleSharePage)
s.mux.HandleFunc("GET /s/{token}/stream", s.handleShareStream)
+ s.mux.HandleFunc("GET /s/{token}/thumbnail", s.handleShareThumbnail)
// Static assets (public)
staticHandler := http.FileServer(s.staticFS)