mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-29 06:37:17 +01:00
feat(jellyfin): implement GET /Playlists/{id} for playlist visibility
Finamp's playlist edit screen calls GET /Playlists/{id} to read the
OpenAccess (public visibility) flag; we only had the sub-routes, so it
404'd and the edit screen failed to load. Return the Jellyfin PlaylistDto
shape (OpenAccess from Public, empty Shares, media item ids).
This commit is contained in:
parent
b3247591cc
commit
ead911a9e1
4 changed files with 61 additions and 0 deletions
|
|
@ -80,6 +80,7 @@ func (api *Router) routes() http.Handler {
|
|||
r.Get("/MusicGenres", api.getGenres)
|
||||
|
||||
r.Post("/Playlists", api.createPlaylist)
|
||||
r.Get("/Playlists/{playlistId}", api.getPlaylist)
|
||||
r.Get("/Playlists/{playlistId}/Items", api.getPlaylistItems)
|
||||
r.Post("/Playlists/{playlistId}/Items", api.addToPlaylist)
|
||||
r.Delete("/Playlists/{playlistId}/Items", api.removeFromPlaylist)
|
||||
|
|
|
|||
|
|
@ -83,6 +83,15 @@ type PlaylistUserPermissions struct {
|
|||
CanEdit bool `json:"CanEdit"`
|
||||
}
|
||||
|
||||
// PlaylistInfo is the response shape for GET /Playlists/{id}. Finamp reads OpenAccess to show a
|
||||
// playlist's public-visibility toggle; ItemIds are the playlist's media item ids (matching real
|
||||
// Jellyfin, which returns item ids here — not playlist-entry ids).
|
||||
type PlaylistInfo struct {
|
||||
OpenAccess bool `json:"OpenAccess"`
|
||||
Shares []PlaylistUserPermissions `json:"Shares"`
|
||||
ItemIds []string `json:"ItemIds"`
|
||||
}
|
||||
|
||||
type QueryResult struct {
|
||||
Items []BaseItemDto `json:"Items"`
|
||||
TotalRecordCount int `json:"TotalRecordCount"`
|
||||
|
|
|
|||
|
|
@ -47,6 +47,25 @@ func trackToBaseItem(t model.PlaylistTrack) dto.BaseItemDto {
|
|||
return item
|
||||
}
|
||||
|
||||
// getPlaylist returns a playlist's public-visibility flag and item ids. Finamp calls this before
|
||||
// opening a playlist's edit screen to read OpenAccess. Visibility is enforced by GetWithTracks
|
||||
// (public or owned by the current user); any error maps to 404 so private playlists can't be probed.
|
||||
func (api *Router) getPlaylist(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
id := dto.DecodeID(chi.URLParam(r, "playlistId"))
|
||||
pls, err := api.playlists.GetWithTracks(ctx, id)
|
||||
if err != nil {
|
||||
http.Error(w, "Not Found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
itemIds := slice.Map(pls.Tracks, func(t model.PlaylistTrack) string { return dto.EncodeID(t.MediaFileID) })
|
||||
api.ok(w, r, dto.PlaylistInfo{
|
||||
OpenAccess: pls.Public,
|
||||
Shares: []dto.PlaylistUserPermissions{},
|
||||
ItemIds: itemIds,
|
||||
})
|
||||
}
|
||||
|
||||
// getPlaylistItems relies on core/playlists.GetWithTracks to enforce playlist visibility
|
||||
// (public or owned by the current user); any error — not found or not visible — is reported as
|
||||
// a generic 404 so a playlist id can't be used to probe for the existence of private playlists.
|
||||
|
|
|
|||
|
|
@ -181,6 +181,38 @@ var _ = Describe("Playlists", func() {
|
|||
})
|
||||
})
|
||||
|
||||
Describe("getPlaylist", func() {
|
||||
It("returns OpenAccess from Public and item ids (encoded media file ids, not entry ids)", func() {
|
||||
fp.getPls = &model.Playlist{
|
||||
ID: "pl1",
|
||||
Public: true,
|
||||
Tracks: model.PlaylistTracks{
|
||||
{ID: "1", MediaFileID: "s1", PlaylistID: "pl1", MediaFile: model.MediaFile{ID: "s1"}},
|
||||
{ID: "2", MediaFileID: "s2", PlaylistID: "pl1", MediaFile: model.MediaFile{ID: "s2"}},
|
||||
},
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/Playlists/pl1", nil).WithContext(context.Background())
|
||||
r = withChiURLParam(r, "playlistId", "pl1")
|
||||
api.getPlaylist(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
var res dto.PlaylistInfo
|
||||
Expect(json.Unmarshal(w.Body.Bytes(), &res)).To(Succeed())
|
||||
Expect(res.OpenAccess).To(BeTrue())
|
||||
Expect(res.Shares).To(BeEmpty())
|
||||
Expect(res.ItemIds).To(Equal([]string{dto.EncodeID("s1"), dto.EncodeID("s2")}))
|
||||
})
|
||||
|
||||
It("returns 404 for a non-owned or absent playlist", func() {
|
||||
fp.getErr = model.ErrNotFound
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/Playlists/missing", nil).WithContext(context.Background())
|
||||
r = withChiURLParam(r, "playlistId", "missing")
|
||||
api.getPlaylist(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusNotFound))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("addToPlaylist", func() {
|
||||
It("adds tracks by song id from the lowercase ids param real Jellyfin clients send", func() {
|
||||
w := httptest.NewRecorder()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue