diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-02 09:33:22 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-02 09:33:22 +0300 |
| commit | 95c6ce72bdf16ead39d0df1ee27b0474441fbe4f (patch) | |
| tree | f6823246f8e7f85db2233fec461159005ffadc41 /internal/api | |
| parent | 4dd62a8b61c56ef28f5f0cab58b8c2da94806717 (diff) | |
fix:x
Diffstat (limited to 'internal/api')
| -rw-r--r-- | internal/api/handlers_more_test.go | 36 | ||||
| -rw-r--r-- | internal/api/handlers_share.go | 60 | ||||
| -rw-r--r-- | internal/api/handlers_test.go | 7 | ||||
| -rw-r--r-- | internal/api/server.go | 1 |
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) |
